Skip to content

Commit

Permalink
[Fix/error handling] Different error handling for is_ec and is_es (#221)
Browse files Browse the repository at this point in the history
* QRCode features (#217)

* feat: changed qrcode handling

* feat: copied static files from Satosa-saml2spid

* feat: modified expiration time handling

* fix: update test configuration

* fix: remove of connection params

* [Feat/retention rule] Added ttl rule for sessions (#218)

* feat: added retention rule for session collection

* test: added test for retention rule

* Update pyeudiw/storage/mongo_storage.py

Co-authored-by: Giuseppe De Marco <giuseppe.demarco@teamdigitale.governo.it>

* Update pyeudiw/storage/mongo_storage.py

Co-authored-by: Giuseppe De Marco <giuseppe.demarco@teamdigitale.governo.it>

* Update pyeudiw/storage/base_storage.py

Co-authored-by: Giuseppe De Marco <giuseppe.demarco@teamdigitale.governo.it>

* chore: added config parameter

---------

Co-authored-by: Giuseppe De Marco <giuseppe.demarco@teamdigitale.governo.it>

* fix: error handling

---------

Co-authored-by: Giuseppe De Marco <giuseppe.demarco@teamdigitale.governo.it>
Co-authored-by: Ghenadie Artic <57416779+Gartic99@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 10, 2024
1 parent f75b6df commit fbf62c4
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 33 deletions.
2 changes: 2 additions & 0 deletions example/satosa/pyeudiw_backend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ config:
qrcode:
size: 100
color: '#2B4375'
expiration_time: 120 # seconds
logo_path:
use_zlib: false

Expand Down Expand Up @@ -137,6 +138,7 @@ config:
db_sessions_collection: sessions
db_trust_attestations_collection: trust_attestations
db_trust_anchors_collection: trust_anchors
data_ttl: 63072000 # 2 years
# - connection_params:

#This is the configuration for the relaying party metadata
Expand Down
14 changes: 6 additions & 8 deletions example/satosa/templates/qr_code.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
<p id="content-qrcode-info-text" class="text-helper text-info">
Il codice è valido per <b id="timer"></b> secondi
</p>
<p class="">
<a href="javascript:window.location.reload(true)"><b>Ho bisogno di più tempo</b></a>
</p>
<p id="content-qrcode-subtitle" class="qr-code-text" hidden>
Puoi trovarla direttamente all'interno dell'app IO.
Se hai più dispositivi mobili,
Expand All @@ -42,7 +39,6 @@
<span>Indietro</span>
</button>
</div>

</div>
</div>
</div>
Expand All @@ -65,17 +61,17 @@

let startingConnectionTitle = "Continua sul tuo smartphone";
let startingConnectionText = "Per proseguire, segui le istruzioni sull'app IO e autorizza l'accesso";
let clickAccessLabel = "Clicca qui per accedere al servizio";
let startingConnectionQRInfo = "<b>Non hai ricevuto la notifica?</b>";
let clickAccessLabel = "Clicca qui per accedere al servizio";
let connectedTitle = "Autenticazione completata";
let qrCodeExpiredInfo = "Il codice QR non è più valido";

let expirationTime = 120;
let expirationTime = "{{ qrcode_expiration_time }}";
let pollingInterval;

function StartQRcodeScanCheck(){
$('#timer').text(expirationTime);
$('#timer').text(expirationTime);
let timeout = expirationTime*1000;
// setTimeout(timeout);
pollingInterval = setTimeout(QRcodeScanCheck, timeout);
}

Expand Down Expand Up @@ -115,6 +111,8 @@

function QRcodeExpired(){
console.log('session expired');
blankQRcode();
clearInterval(countdown);
changeQrCodeInfo(qrCodeExpiredInfo);
}

Expand Down
30 changes: 13 additions & 17 deletions pyeudiw/federation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
from .exceptions import InvalidEntityStatement, InvalidEntityConfiguration
from pyeudiw.federation.schemas.entity_configuration import EntityStatementPayload, EntityConfigurationPayload


def is_es(payload: dict) -> bool:
def is_es(payload: dict) -> None:
"""
Determines if payload dict is a Subordinate Entity Statement
:param payload: the object to determine if is a Subordinate Entity Statement
:type payload: dict
:returns: True if is an Entity Statement and False otherwise
:rtype: bool
"""

try:
EntityStatementPayload(**payload)
if payload["iss"] != payload["sub"]:
return True
except Exception:
return False


def is_ec(payload: dict) -> bool:
if payload["iss"] == payload["sub"]:
_msg = f"Invalid Entity Statement: iss and sub cannot be the same"
raise InvalidEntityStatement(_msg)
except ValueError as e:
_msg = f"Invalid Entity Statement: {e}"
raise InvalidEntityStatement(_msg)

def is_ec(payload: dict) -> None:
"""
Determines if payload dict is an Entity Configuration
:param payload: the object to determine if is an Entity Configuration
:type payload: dict
:returns: True if is an Entity Configuration and False otherwise
:rtype: bool
"""

try:
EntityConfigurationPayload(**payload)
return True
except Exception as e:
return False
except ValueError as e:
_msg = f"Invalid Entity Configuration: {e}"
raise InvalidEntityConfiguration(_msg)
8 changes: 6 additions & 2 deletions pyeudiw/federation/trust_chain_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
HttpError,
MissingTrustAnchorPublicKey,
TimeValidationError,
KeyValidationError
KeyValidationError,
InvalidEntityStatement
)

from pyeudiw.jwk import find_jwk
Expand Down Expand Up @@ -239,8 +240,11 @@ def _update_st(self, st: str) -> str:
"""
payload = decode_jwt_payload(st)
iss = payload['iss']
if not is_es(payload):

try:
is_es(payload)
# It's an entity configuration
except InvalidEntityStatement:
return self._retrieve_ec(iss)

# if it has the source_endpoint let's try a fast renewal
Expand Down
1 change: 1 addition & 0 deletions pyeudiw/satosa/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def pre_request_endpoint(self, context: Context, internal_request, **kwargs) ->
{
"qrcode_color" : self.config["qrcode"]["color"],
"qrcode_text": res_url,
"qrcode_expiration_time": self.config["qrcode"]["expiration_time"],
"state": state,
"status_endpoint": self.absolute_status_url
}
Expand Down
18 changes: 18 additions & 0 deletions pyeudiw/storage/base_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ def init_session(self, document_id: str, dpop_proof: dict, attestation: dict) ->
:param attestation: the attestation.
"""
raise NotImplementedError()

def set_session_retention_ttl(self, ttl: int) -> None:
"""
Set the database retention ttl.
:param ttl: the ttl.
:type ttl: int | None
"""
raise NotImplementedError()

def has_session_retention_ttl(self) -> bool:
"""
Check if the session has a retention ttl.
:returns: True if the session has a retention ttl, False otherwise.
:rtype: bool
"""
raise NotImplementedError()

def add_dpop_proof_and_attestation(self, document_id, dpop_proof: dict, attestation: dict) -> UpdateResult:
"""
Expand Down
18 changes: 17 additions & 1 deletion pyeudiw/storage/mongo_storage.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pymongo
import datetime as dt
from datetime import datetime

from pymongo.results import UpdateResult
Expand Down Expand Up @@ -26,6 +27,8 @@ def __init__(self, conf: dict, url: str, connection_params: dict = {}) -> None:
self.client = None
self.db = None

self.set_session_retention_ttl(conf.get("data_ttl", None))

@property
def is_connected(self) -> bool:
if not self.client:
Expand Down Expand Up @@ -109,7 +112,7 @@ def get_by_state_and_session_id(self, state: str, session_id: str = "") -> Union
def init_session(self, document_id: str, session_id: str, state: str) -> str:
entity = {
"document_id": document_id,
"creation_date": datetime.now().isoformat(),
"creation_date": dt.datetime.now(tz=dt.timezone.utc),
"state": state,
"session_id": session_id,
"finalized": False,
Expand All @@ -124,6 +127,19 @@ def init_session(self, document_id: str, session_id: str, state: str) -> str:
self.sessions.insert_one(entity)

return document_id

def set_session_retention_ttl(self, ttl: int) -> None:
self._connect()

if not ttl:
if self.sessions.index_information().get("creation_date_1"):
self.sessions.drop_index("creation_date_1")
else:
self.sessions.create_index([("creation_date", pymongo.ASCENDING)], expireAfterSeconds=ttl)

def has_session_retention_ttl(self) -> bool:
self._connect()
return self.sessions.index_information().get("creation_date_1") is not None

def add_dpop_proof_and_attestation(self, document_id: str, dpop_proof: dict, attestation: dict) -> UpdateResult:
self._connect()
Expand Down
15 changes: 11 additions & 4 deletions pyeudiw/tests/federation/test_schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

from pyeudiw.tools.utils import iat_now, exp_from_now
from pyeudiw.federation import is_es, is_ec
from pyeudiw.federation.exceptions import InvalidEntityStatement, InvalidEntityConfiguration

NOW = iat_now()
EXP = exp_from_now(5)
Expand Down Expand Up @@ -127,16 +128,22 @@


def test_is_es():
assert is_es(ta_es)
is_es(ta_es)


def test_is_es_false():
assert not is_es(ta_ec)
try:
is_es(ta_ec)
except InvalidEntityStatement as e:
pass


def test_is_ec():
assert is_ec(ta_ec)
is_ec(ta_ec)


def test_is_ec_false():
assert not is_ec(ta_es)
try:
is_ec(ta_es)
except InvalidEntityConfiguration as e:
pass
1 change: 1 addition & 0 deletions pyeudiw/tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"qrcode": {
"size": 100,
"color": "#2B4375",
"expiration_time": 120,
},
"jwt": {
"default_sig_alg": "ES256",
Expand Down
29 changes: 28 additions & 1 deletion pyeudiw/tests/storage/test_mongo_storage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import uuid

import time
import pytest

from pyeudiw.storage.mongo_storage import MongoStorage
Expand Down Expand Up @@ -111,3 +111,30 @@ def test_update_response_object(self):
assert document["nonce"] == nonce
assert document["request_object"] == request_object
assert document["internal_response"] == {"response": "test"}


def test_retention_ttl(self):
self.storage.set_session_retention_ttl(5)

assert self.storage.has_session_retention_ttl()

state = str(uuid.uuid4())
session_id = str(uuid.uuid4())

document_id = self.storage.init_session(
str(uuid.uuid4()),
session_id=session_id, state=state)

assert document_id

# MongoDB does not garantee that the document will be deleted at the exact time
# https://www.mongodb.com/docs/v7.0/core/index-ttl/#timing-of-the-delete-operation

document = self.storage.get_by_id(document_id)

while document:
try:
time.sleep(2)
document = self.storage.get_by_id(document_id)
except ValueError:
document = None

0 comments on commit fbf62c4

Please sign in to comment.