Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Added Mongo Storage and cache #31

Merged
merged 28 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
14e8565
Use datetime and uuid4 lib instead of placeholders
salvatorelaiso Jul 21, 2023
8916cdf
Refactor curve param in init function
salvatorelaiso Jul 21, 2023
2977c0f
chore: linting
salvatorelaiso Jul 21, 2023
4e98f24
Set client_id from config metadata for issuer and subject
salvatorelaiso Jul 21, 2023
60f9870
Add default_sign_alg in config
salvatorelaiso Jul 21, 2023
2bb0240
Set htu param in JWT from config
salvatorelaiso Jul 21, 2023
a531dc0
Move signing algorithm into Helper init
salvatorelaiso Jul 21, 2023
1c89900
Merge remote-tracking branch 'upstream/dev' into dev
salvatorelaiso Jul 21, 2023
1545a03
Merge branch 'main' into dev
salvatorelaiso Jul 21, 2023
787e5dc
Removed unecessary directory
pderose Jul 21, 2023
58bc95f
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-python…
pderose Jul 21, 2023
d0ada24
Merge branch 'dev' into dev
PascalDR Jul 21, 2023
029ba28
Update tests
salvatorelaiso Jul 21, 2023
21cd8dd
Merge branch 'dev' of https://github.com/PascalDR/eudi-wallet-it-pyth…
pderose Jul 21, 2023
bd58830
Removed unecessary directory
pderose Jul 21, 2023
45b7112
Initial support to MongoDB storge
pderose Jul 21, 2023
a3e7a06
Added cache support
pderose Jul 24, 2023
8028cb9
Updated file names
pderose Jul 24, 2023
a76a321
Added Mongodb instalation in pipeline
pderose Jul 24, 2023
f26f2c6
Merge branch 'PascalDR-feature/storage' into dev
pderose Jul 24, 2023
c35ae82
Remove unnecessary file
salvatorelaiso Jul 24, 2023
5f898d5
Merge remote-tracking branch 'upstream/dev' into dev
salvatorelaiso Jul 24, 2023
a4a39d0
Commented phpmyadmin section of docker compose
pderose Jul 24, 2023
5fba712
Added requirement
pderose Jul 24, 2023
2dfbce3
Added dependency in CI/CD
pderose Jul 24, 2023
bbcd929
Added mongodb settings in backend configuration
pderose Jul 25, 2023
f1a68d6
Moved dependency from required to extra
pderose Jul 25, 2023
42d403d
Added dependency to CI/CD
pderose Jul 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ jobs:
run: |
sudo apt update
sudo apt install python3-dev python3-pip
- name: Install MongoDB
run: |
sudo apt-get install -y gnupg wget
sudo wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
sudo echo "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/4.4 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
sudo apt-get update
sudo apt-get install -y mongodb-org
- name: Start MongoDB
run: sudo systemctl start mongod
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
27 changes: 14 additions & 13 deletions example/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ services:
networks:
- wordpress-network

phpmyadmin:
depends_on:
- database
image: arm64v8/phpmyadmin:5.2.1-apache
restart: unless-stopped
ports:
- 8081:80
env_file: .env
environment:
PMA_HOST: database
MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}'
networks:
- wordpress-network
#Decomment this sectiom if you need phpmyadmin
#phpmyadmin:
# depends_on:
# - database
# image: phpmyadmin:5.2.1-apache
# restart: unless-stopped
# ports:
# - 8081:80
# env_file: .env
# environment:
# PMA_HOST: database
# MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}'
# networks:
# - wordpress-network

wordpress:
depends_on:
Expand Down
Empty file added pyeudiw/storage/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions pyeudiw/storage/base_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import Callable

class BaseCache():
def try_retrieve(self, object_name: str, on_not_found: Callable[[], str]) -> dict:
raise NotImplementedError()

def overwrite(self, object_name: str, value_gen_fn: Callable[[], str]) -> dict:
raise NotImplementedError()
9 changes: 9 additions & 0 deletions pyeudiw/storage/base_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class BaseStorage(object):
def init_session(self, dpop_proof: dict, attestation: dict):
NotImplementedError()

def update_request_object(self, document_id: str, request_object: dict):
NotImplementedError()

def update_response_object(self, nonce: str, state: str, response_object: dict):
NotImplementedError()
64 changes: 64 additions & 0 deletions pyeudiw/storage/mongo_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pymongo
from datetime import datetime
from typing import Callable

from .base_cache import BaseCache

class MongoCache(BaseCache):
def __init__(self, storage_conf: dict, url: str, connection_params: dict = None) -> None:
super().__init__()

self.storage_conf = storage_conf
self.url = url
self.connection_params = connection_params

self.client = None
self.db = None

def _connect(self):
if not self.client or not self.client.server_info():
self.client = pymongo.MongoClient(self.url, **self.connection_params)
self.db = getattr(self.client, self.storage_conf["db_name"])
PascalDR marked this conversation as resolved.
Show resolved Hide resolved
self.collection = getattr(self.db, "cache_storage")

def try_retrieve(self, object_name: str, on_not_found: Callable[[], str]) -> dict:
self._connect()

query = {"object_name": object_name}

cache_object = self.collection.find_one(query)

if cache_object is None:
creation_date = datetime.timestamp(datetime.now())
cache_object = {
"object_name": object_name,
"data": on_not_found(),
"creation_date": creation_date
}

self.collection.insert_one(cache_object)

return cache_object

def overwrite(self, object_name: str, value_gen_fn: Callable[[], str]) -> dict:
self._connect()

new_data = value_gen_fn()
updated_date = datetime.timestamp(datetime.now())

cache_object = {
"object_name": object_name,
"data": new_data,
"creation_date": updated_date
}

query = {"object_name": object_name}

self.collection.update_one(query, {
"$set": {
"data": new_data,
"creation_date": updated_date
}
})

return cache_object
92 changes: 92 additions & 0 deletions pyeudiw/storage/mongo_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import pymongo
from datetime import datetime

from .base_storage import BaseStorage

class MongoStorage(BaseStorage):
def __init__(self, storage_conf: dict, url: str, connection_params: dict = None) -> None:
super().__init__()

self.storage_conf = storage_conf
self.url = url
self.connection_params = connection_params

self.client = None
self.db = None

def _connect(self):
if not self.client or not self.client.server_info():
self.client = pymongo.MongoClient(self.url, **self.connection_params)
self.db = getattr(self.client, self.storage_conf["db_name"])
self.collection = getattr(self.db, self.storage_conf["db_collection"])

def _retrieve_document_by_id(self, document_id: str) -> dict:
self._connect()

document = self.collection.find_one({"_id": document_id})

if document is None:
raise ValueError(f'Document with id {document_id} not found')

return document

def _retrieve_document_by_nonce_state(self, nonce: str, state: str) -> dict:
self._connect()

query = {"state": state, "nonce": nonce}

document = self.collection.find_one(query)

if document is None:
raise ValueError(f'Document with nonce {nonce} and state {state} not found')

return document

def init_session(self, dpop_proof: dict, attestation: dict):
creation_date = datetime.timestamp(datetime.now())

entity = {
"creation_date": creation_date,
"dpop_proof": dpop_proof,
"attestation": attestation,
"request_object": None,
"response": None
}

self._connect()
document_id = self.collection.insert_one(entity)

return document_id.inserted_id

def update_request_object(self, document_id: str, request_object: dict):
nonce = request_object["nonce"]
state = request_object["state"]

self._connect()
documentStatus = self.collection.update_one(
{"_id": document_id},
{
"$set": {
"nonce": nonce,
"state": state,
"request_object": request_object
}
}
)

return nonce, state, documentStatus

def update_response_object(self, nonce: str, state: str, response_object: dict):
document = self._retrieve_document_by_nonce_state(nonce, state)

document_id = document["_id"]

documentStatus = self.collection.update_one(
{"_id": document_id},
{"$set":
{
"response_object": response_object
},
})

return nonce, state, documentStatus
Empty file.
49 changes: 49 additions & 0 deletions pyeudiw/tests/storage/test_mongo_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import uuid
import pytest

from pyeudiw.storage.mongo_cache import MongoCache

class TestMongoCache:
@pytest.fixture(autouse=True)
def create_storage_instance(self):
self.cache = MongoCache(
{"db_name": "eudiw"},
"mongodb://localhost:27017/",
{}
)

def test_try_retrieve(self):
object_name = str(uuid.uuid4())
data = str(uuid.uuid4())

obj = self.cache.try_retrieve(object_name, lambda : data)

assert obj
assert obj["object_name"] == object_name
assert obj["data"] == data
assert obj["creation_date"]

query = {"object_name": object_name}

cache_object = self.cache.collection.find_one(query)

assert obj == cache_object

def test_overwrite(self):
object_name = str(uuid.uuid4())
data = str(uuid.uuid4())

obj = self.cache.try_retrieve(object_name, lambda : data)

data_updated = str(uuid.uuid4())

updated_obj = self.cache.overwrite(object_name, lambda : data_updated)

assert obj["data"] != updated_obj["data"]
assert obj["creation_date"] != updated_obj["creation_date"]

query = {"object_name": object_name}
cache_object = self.cache.collection.find_one(query)

assert cache_object["data"] == updated_obj["data"]
assert cache_object["creation_date"] == updated_obj["creation_date"]
89 changes: 89 additions & 0 deletions pyeudiw/tests/storage/test_mongo_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import uuid
import pytest

from pyeudiw.storage.mongo_storage import MongoStorage

class TestMongoStorage:
@pytest.fixture(autouse=True)
def create_storage_instance(self):
self.storage = MongoStorage(
{"db_name": "eudiw", "db_collection": "test"},
"mongodb://localhost:27017/",
{}
)

def test_mongo_connection(self):
self.storage._connect()

assert self.storage.db is not None
assert self.storage.client
assert self.storage.collection is not None

def test_entity_initialization(self):
document_id = self.storage.init_session({"dpop": "test"}, {"attestation": "test"})

assert document_id

document = self.storage._retrieve_document_by_id(document_id)

assert document
assert document["dpop_proof"]
assert document["dpop_proof"] == {"dpop": "test"}
assert document["attestation"]
assert document["attestation"] == {"attestation": "test"}

def test_add_request_object(self):
document_id = self.storage.init_session({"dpop": "test"}, {"attestation": "test"})

assert document_id

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

request_object = {"nonce": nonce, "state": state}

self.storage.update_request_object(document_id, request_object)

document = self.storage._retrieve_document_by_id(document_id)

assert document
assert document["dpop_proof"]
assert document["dpop_proof"] == {"dpop": "test"}
assert document["attestation"]
assert document["attestation"] == {"attestation": "test"}
assert document["state"]
assert document["state"] == state
assert document["state"]
assert document["nonce"] == nonce
assert document["request_object"] == request_object

def test_update_responnse_object(self):
document_id = self.storage.init_session({"dpop": "test"}, {"attestation": "test"})

assert document_id

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

request_object = {"nonce": nonce, "state": state}

self.storage.update_request_object(document_id, request_object)
documentStatus = self.storage.update_response_object(nonce, state, {"response": "test"})

assert documentStatus

document = self.storage._retrieve_document_by_id(document_id)

assert document
assert document["dpop_proof"]
assert document["dpop_proof"] == {"dpop": "test"}
assert document["attestation"]
assert document["attestation"] == {"attestation": "test"}
assert document["state"]
assert document["state"] == state
assert document["state"]
assert document["nonce"] == nonce
assert document["request_object"] == request_object
assert document["response_object"]
assert document["response_object"] == {"response": "test"}

3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def readme():
install_requires=[
"cryptojwt>=1.8.2,<1.9",
"qrcode>=7.4.2,<7.5",
"pydantic>=2.0,<2.2"
"pydantic>=2.0,<2.2",
"pymongo>=4.4.1,<4.5"
],
extra_require={
"satosa": [
Expand Down
Loading