This application is an adapter for Pääsuke written in Python Flask and Postgres functions.
This can be used (as a baseline) by a party who keeps mandates on their side and offers a standard web service for Pääsuke for it to add new mandates, query existing mandates end their validity.
The application uses a Postgres database for storing the data. Into that Postgres database, the scripts create some views and stored functions that the Python app calls. So anyone who keeps their data in a relation database could adapt a similar pattern and create their own database views and stored functions.
Every time this repository is tag-ed with a new version, GitHub Actions builds a public Docker image and uploads it to GitHub Container Registry (ghcr.io) See the details of recent actions and the list of tags.
For example, one can get the version 0.9.0 like this:
docker pull ghcr.io/e-gov/ph-sample-provider-python:v0.9.0
docker pull --platform linux/x86_64 ghcr.io/e-gov/ph-sample-provider-python:v0.9.0 (Mac)
Use Docker-compose to start up a Postgres database and the application. The scripts create a few tables to sore information about mandates and views and functions to interact with this data.
`python3 -m venv venv`
`source venv/bin/activate`
`pip install -r requirements.txt`
`export PYTHONPATH=$PWD`
`python -m venv venv`
`venv\Scripts\activate`
`pip install -r requirements.txt`
`set PYTHONPATH=%cd%`
`cp config/example.cfg config/dev.cfg`
`export APP_SETTINGS=../config/dev.cfg`
`python3 api/app.py`
`copy config\example.cfg config\dev.cfg`
`set APP_SETTINGS=..\config\dev.cfg`
`python api\app.py`
`docker-compose up -d`
Configure the list of roles in tests/pg_data/02b_view_paasuke_roles_view.sql
Tests are using the Postgres database running on a Docker container (so you need to have docker-compose up postgres)
Database contains fixture data .
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export PYTHONPATH=$PWD
export APP_SETTINGS=../config/test.cfg
pytest
`pip install gunicorn`
Point gunicorn to WSGI entrypoint wsgi.py
`gunicorn --bind 127.0.0.1:5001 wsgi:application`
When running locally then access API on http://localhost:8082
Accepts representee identifier as parameter in path
Raises 400 error if identifier is not valid
Returns the list of MandateTriplets with status code 200 if representee has valid mandates
Returns empty list if the representee has no valid mandates or the representee is unknown.
Selects data from Postgres view paasuke_mandates_view
Example:
curl --location 'http://localhost:8082/v1/representees/EE44444444/delegates/mandates' \
--header 'X-Road-UserId: test-header-xroad-userid' \
--header 'X-Road-Represented-Party: test-header-xroad-represented-party' \
--header 'X-Road-Id: test-header-xroad-id'
Accepts delegate identifier as parameter in path
Raises 400 error if identifier is not valid
Returns the list of MandateTriplets with status code 200 if delegate has valid mandates
Returns an empty list if delegate has no valid mandates or the delegate is unknown.
Selects data from Postgres view paasuke_mandates_view
Example:
curl --location 'http://127.0.0.1:8082/v1/delegates/EE22202222222/representees/mandates' \
--header 'X-Road-UserId: Test User Id'
Get a list of roles
Selects data from Postgres view paasuke_roles_view
Example:
curl --location 'http://localhost:8082/v1/roles'
Accepts repreentee and delegate identifiers as path parameters
Raises 400 error if payload data is not valid or identifiers are not valid
Raises 422 error if Postgres function paasuke_add_mandate does not validate input data.
Return an empty list with status code 201 in case of success
Example of successful request:
curl --location 'http://127.0.0.1:8082/v1/representees/EE12345678/delegates/EE38302250123/mandates' \
--header 'Content-Type: application/json' \
--header 'X-Road-UserId: LT123456' \
--header 'X-Road-Represented-Party: LV1234566' \
--data ' {
"authorizations": [
{
"hasRole": "BR_REPRIGHT:JUHL_SOLEREP",
"userIdentifier": "EE49028099999"
}
],
"representee": {
"identifier": "EE12345678",
"legalName": "Väikefirma OÜ",
"type": "LEGAL_PERSON"
},
"delegate": {
"firstName": "Jüri",
"identifier": "EE38302250123",
"surname": "Juurikas",
"type": "NATURAL_PERSON"
},
"document": {
"singleDelegate": true,
"uuid": "5b72e01c-fa7f-479c-b014-cc19efe5b732"
},
"mandate": {
"canSubDelegate": true,
"role": "AGENCY_X:MANDATES_MANAGER",
"validityPeriod": {
"from": "2028-01-01",
"through": "2030-12-31"
}
}
}'
Accepts representeeId, delegateId, mandateId as parameters in the path.
Raises 404 error if the mandate does not exist
Raises 422 error if Postgres function paasuke_add_mandate_subdelegate does not validate input data.
Returns empty list with status code 200 in success case
Example of successful request:
curl --location 'http://127.0.0.1:8082/v1/representees/100004/delegates/100005/mandates/150003/subdelegates' \
--header 'Content-Type: application/json' \
--header 'X-Road-UserId: EE23232323' \
--header 'X-Road-Represented-Party: EE2323224444' \
--data '{
"authorizations": [
{
"hasRole": "BR_REPRIGHT:PROK_SOLEREP",
"userIdentifier": "EE39912310123"
}
],
"document": {
"singleDelegate": true,
"uuid": "5b72e01c-fa7f-479c-b014-cc19efe5b732"
},
"subDelegate": {
"firstName": "Jüri",
"identifier": "EE38302250123",
"surname": "Juurikas",
"type": "NATURAL_PERSON"
},
"validityPeriod": {
"from": "2028-01-01",
"through": "2030-12-31"
}
}'
Accepts representeeId, delegateId, mandateId as parameters in the path.
Raises 404 error if the mandate does not exist
Raises 422 error if Postgres function paasuke_delete_mandate does not validate input data.
Returns empty list with status code 200 if mandates has been marked deleted successfully
Example of successful request:
curl --location --request PUT 'http://127.0.0.1:8082/v1/representees/100001/delegates/100003/mandates/150002' \
--header 'Content-Type: application/json' \
--data '{
"action": "DELETE",
"authorizations": [
{
"userIdentifier": "EE39912310123",
"hasRole": "BR_REPRIGHT:SOLEREP"
}
],
"document": {
"uuid": "5b72e01c-fa7f-479c-b014-cc19efe5b732",
"singleDelegate": true
}
}'
GET /v1/delegates/<str:delegate>/representees/mandates- Request parameter
subDelegatedByis not used
- Request parameter
GET /v1/representees/<str:representee>/delegates/mandates- Request parameter
subDelegatedByis not used
- Request parameter
GET /roles- Response RoleDefinition contains
canSubDelegateproperty - Response RoleDefinition contains
modifiedproperty - Response RoleDefinition does not contain
representeeIdentifierInproperty - Response RoleDefinition
representeeTypeproperty containsGORVENRMENT_PERSONenum - Response RoleDefinition does not contain
subDelegableproperty - Response RoleDefinition does not contain
subDelegatingMustBeSignedproperty - Response RoleDefinition contains
assignableByproperty
- Response RoleDefinition contains
