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

Pstlouis/fix statuslist #4

Merged
merged 3 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Traceability Controller
TRACEABILITY_CONTROLLER_DOMAIN=''

# Traction
TRACTION_API_URL='https://traction-sandbox-tenant-proxy.apps.silver.devops.gov.bc.ca'
TRACTION_TENANT_ID=''
TRACTION_API_KEY=''

# Temporary verifier agent endpoint
# until next release (Aca-py admin v0.11)
VERIFIER_ENDPOINT='http://verifier-agent:8020'

# Postgres
POSTGRES_URI='postgres://postgres:postgres@postgres:5432'
POSTGRES_USER='postgres'
POSTGRES_PASSWORD='postgres'

# Letsencrypt
LETSENCRYPT_EMAIL=''
70 changes: 70 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
version: '3'
services:

agent:
image: bcgovimages/aries-cloudagent:py3.9-indy-1.16.0_0.12.0rc2
ports:
- 8020:8020
entrypoint: ["aca-py", "start"]
command: [
'--no-ledger',
'--admin', '0.0.0.0', '8020',
'--admin-insecure-mode',
'--endpoint', 'http://agent:8021',
'--outbound-transport', 'http',
'--inbound-transport', 'http', '0.0.0.0', '8021'
]

postgres:
image: postgres:16-alpine
ports:
- 5432:5432
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

traceability-api:
image: patstlouis91/traceability-controller:interop-0.0.1
# build:
# context: ./traceability-controller
ports:
- 8000:8000
entrypoint: ["python", "main.py"]
environment:
POSTGRES_URI: ${POSTGRES_URI}
TRACTION_API_KEY: ${TRACTION_API_KEY}
TRACTION_TENANT_ID: ${TRACTION_TENANT_ID}
TRACTION_API_ENDPOINT: ${TRACTION_API_ENDPOINT}
VERIFIER_ENDPOINT: ${VERIFIER_ENDPOINT}
TRACEABILITY_CONTROLLER_DOMAIN: ${TRACEABILITY_CONTROLLER_DOMAIN}
labels:
- traefik.enable=true
- traefik.http.routers.traceability-api.rule=Host(`${TRACEABILITY_CONTROLLER_DOMAIN}`)
- traefik.http.routers.traceability-api.entrypoints=websecure
- traefik.http.routers.traceability-api.tls.certresolver=myresolver
- traefik.http.services.traceability-api.loadbalancer.server.port=8000

traefik:
image: traefik:v2.10
restart: always
security_opt:
- no-new-privileges:true
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.websecure.address=:443
- --certificatesresolvers.myresolver.acme.tlschallenge=true
- --certificatesresolvers.myresolver.acme.email=${LETSENCRYPT_EMAIL}
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
ports:
- 443:443
volumes:
- letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro

volumes:
postgres_data:
letsencrypt:
17 changes: 17 additions & 0 deletions traceability-controller/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM python:3.11-slim

WORKDIR /traceability-controller

ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

COPY requirements.txt ./

RUN pip install --upgrade pip
RUN pip install -r requirements.txt

COPY app ./app
COPY app config.py main.py ./

CMD [ "python", "main.py" ]
111 changes: 39 additions & 72 deletions traceability-controller/app/controllers/askar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,50 @@
from config import settings
import time
from app.validations import ValidationException


def holderClientHashesDataKey(did_label):
"""OAuth client who will send presentations `/presentations`"""
return f"holderClientHashes"


def issuerClientHashesDataKey():
"""OAuth client who will issue credentials `/credentials/issue`"""
return f"issuerClientHashes"


def didDocumentDataKey(did_label):
"""Controller documents for web DIDs"""
return f"didDocuments:{did_label}"


def statusEntriesDataKey(did_label, statusCredentialId):
"""List of registered indexes in a status list"""
return f"statusEntries:{did_label}:{statusCredentialId}"


def statusCredentialDataKey(did_label, statusCredentialId):
"""Status list credential maintained by an issuer"""
return f"statusCredentials:{did_label}:{statusCredentialId}"


def issuedCredentialDataKey(did_label, credentialId):
"""Issued credentials of an issuer"""
return f"issuedCredentials:{did_label}:{credentialId}"


def recievedCredentialDataKey(did_label, credentialId):
"""Credentials recieved through a presentation exchange"""
return f"storedCredentials:{did_label}:{credentialId}"


async def provision_public_store():
await Store.provision(
settings.ASKAR_PUBLIC_STORE,
"raw",
settings.ASKAR_PUBLIC_STORE_KEY,
recreate=False,
)


async def open_store(key):
return await Store.open(settings.ASKAR_PUBLIC_STORE, "raw", key)


async def store_data(storeKey, dataKey, data):
store = await open_store(storeKey)
async with store.session() as session:
await session.insert(
"seq",
dataKey.lower(),
json.dumps(data),
{"~plaintag": "a", "enctag": "b"},
from aries_askar.bindings import generate_raw_key

class AskarController:

def __init__(self, db=settings.ASKAR_DEFAULT_DB):
self.db = f'{settings.POSTGRES_URI}/{db}'
self.key = generate_raw_key(settings.TRACTION_API_KEY)

async def provision(self):
await Store.provision(
self.db,
"raw",
self.key,
recreate=False,
)

async def open(self):
return await Store.open(self.db, "raw", self.key)

async def fetch_data(storeKey, dataKey):
store = await open_store(storeKey)
async with store.session() as session:
data = await session.fetch("seq", dataKey.lower())
return json.loads(data.value)
async def fetch(self, data_key):
store = await self.open()
async with store.session() as session:
data = await session.fetch("seq", data_key)
return json.loads(data.value)

async def store(self, data_key, data):
store = await self.open()
async with store.session() as session:
await session.insert(
"seq",
data_key,
json.dumps(data),
{"~plaintag": "a", "enctag": "b"},
)

async def update_data(storeKey, dataKey, new_data):
store = await open_store(storeKey)
async with store.session() as session:
await session.replace(
"seq",
dataKey.lower(),
json.dumps(new_data),
{"~plaintag": "a", "enctag": "b"},
)
async def update(self, data_key, data):
store = await self.open()
async with store.session() as session:
await session.replace(
"seq",
data_key,
json.dumps(data),
{"~plaintag": "a", "enctag": "b"},
)


async def find_api_key(storeKey, apiKeyHash):
Expand Down
48 changes: 26 additions & 22 deletions traceability-controller/app/controllers/auth.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
from app.controllers import askar
from app.controllers.askar import AskarController
from config import settings
import uuid
import hashlib
import secrets
from app.validations import ValidationException
from app.auth.handler import decodeJWT
from app.utils import did_from_label


def can_issue(credential, did_label):
"""Function to check if the issuer field matches the organization's instance"""
did = (
credential["issuer"]
if isinstance(credential["issuer"], str)
else credential["issuer"]["id"]
)
if did != did_from_label(did_label):
raise ValidationException(
status_code=400, content={"message": "Invalid issuer"}
)
return did


async def verify_client_hash(client_id, client_secret):
try:
client_secret_hash = hashlib.sha256(client_secret.encode('utf-8')).hexdigest()
data_key = f'issuerClients:{client_id}'
client_data = await askar.fetch_data(settings.ASKAR_PUBLIC_STORE_KEY, data_key)
if client_data['client_secret_hash'] != client_secret_hash:
client_hash = uuid.uuid5(uuid.UUID(client_id), client_secret)
if await AskarController().fetch(f'clientHash:{client_id}') != str(client_hash):
raise ValidationException(
status_code=400,
content={"message": "Invalid client"},
Expand All @@ -26,26 +39,17 @@ async def verify_client_hash(client_id, client_secret):

async def is_authorized(did_label, request):
token = request.headers.get("Authorization").replace("Bearer ", "")
client_id = decodeJWT(token)["client_id"]
data_key = f'issuerClients:{client_id}'
client_data = await askar.fetch_data(settings.ASKAR_PUBLIC_STORE_KEY, data_key)
if client_data['did_label'] != did_label:
request_client_id = decodeJWT(token)["client_id"]
stored_client_id = await AskarController(db=did_label).fetch('clientId')
if request_client_id != stored_client_id:
raise ValidationException(status_code=401, content={"message": "Unauthorized"})


async def new_issuer_client(did_label):
client_id = uuid.uuid4()
client_secret = secrets.token_urlsafe(24)
client_secret_hash = hashlib.sha256(client_secret.encode('utf-8')).hexdigest()
client_data = {
'did_label': did_label,
'client_secret_hash': client_secret_hash
}
data_key = f'issuerClients:{client_id}'
await askar.store_data(settings.ASKAR_PUBLIC_STORE_KEY, data_key, client_data)

# Provision private store
# askarKey = generate_askar_key(client_secret)
# await askar.provision_store(askarKey)

return client_id, client_secret
client_hash = uuid.uuid5(client_id, client_secret)
await AskarController().store(f'clientHash:{str(client_id)}', str(client_hash))
await AskarController(db=did_label).store('clientId', str(client_id))

return str(client_id), client_secret
50 changes: 50 additions & 0 deletions traceability-controller/app/controllers/did_document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import json
from aries_askar import Store, error
from config import settings
import time
from app.validations import ValidationException
from app.utils import did_from_label
from app.controllers.askar import AskarController



class DidDocumentController:

def __init__(self, did_label):
self.did_label = did_label
self.id = did_from_label(did_label)
self.context = [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/v2",
"https://w3id.org/traceability/v1"
]
self.verification_method = []
self.authentication = []
self.assertion_method = []
service = {
'id': f'{self.id}#traceability-api',
'type': ["TraceabilityAPI"],
'serviceEndpoint': f'{settings.HTTPS_BASE}/{settings.DID_NAMESPACE}/{did_label}'
}
self.service = [service]

async def add_verkey(self):
verification_method = {
'id': f'{self.id}#verkey',
'type': 'Ed25519VerificationKey2018',
'controller': self.id,
'publicKeyBase58': await AskarController(self.did_label).fetch('verkey')
}
self.verification_method = [verification_method]
self.authentication = [verification_method['id']]
self.assertion_method = [verification_method['id']]

def as_json(self):
return {
'@context': self.context,
'id': self.id,
'verificationMethod': self.verification_method,
'authentication': self.authentication,
'assertionMethod': self.assertion_method,
'service': self.service,
}
Loading