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

Status list plugin for ACA-Py #1439

Closed
wants to merge 7 commits into from
Closed
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
4 changes: 2 additions & 2 deletions oid4vc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ The Plugin expects the following configuration options. These options can either
- Port used for the OpenID4VCI public server
- `OID4VCI_ENDPOINT` or `oid4vci.endpoint`
- `credential_issuer` endpoint, seen in the Credential Offer
- `OID4VCI_CRED_HANDLER` or `oid4vci.cred_handler`
- Dict of credential handlers. e.g. `{"jwt_vc_json": "jwt_vc_json"}`
- `OID4VCI_STATUS_HANDLER` or `oid4vci.status_handler`
- Status Handler used for handling credential status

### Creating Supported Credential Records

Expand Down
20 changes: 19 additions & 1 deletion oid4vc/jwt_vc_json/cred_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from oid4vc.models.supported_cred import SupportedCredential
from oid4vc.pop_result import PopResult
from oid4vc.public_routes import types_are_subset
from oid4vc.status_handler import StatusHandler

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -73,6 +74,13 @@ async def issue(
"sub": subject,
}

status_handler = context.inject_or(StatusHandler)
if credential_status := await status_handler.assign_status_entries(
context, supported.supported_cred_id, ex_record.exchange_id, "w3c"
):
payload["vc"]["credentialStatus"] = credential_status
LOGGER.debug("signed credential: %s", payload)

jws = await jwt_sign(
context.profile,
{},
Expand All @@ -82,7 +90,9 @@ async def issue(

return jws

def validate_credential_subject(self, supported: SupportedCredential, subject: dict):
def validate_credential_subject(
self, supported: SupportedCredential, subject: dict
):
"""Validate the credential subject."""
pass

Expand Down Expand Up @@ -114,3 +124,11 @@ async def verify_presentation(
) -> VerifyResult:
"""Verify a presentation in JWT VP format."""
return await self.verify(profile, presentation)

async def assign_status_entries(
self, context: AdminRequestContext, supported_cred_id: str
) -> OID4VCIExchangeRecord:
"""Assign status entries."""

status_handler = StatusHandler(context)
return await status_handler.assign_status_entries(supported_cred_id, "w3c")
3 changes: 3 additions & 0 deletions oid4vc/oid4vc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from jwt_vc_json.cred_processor import JwtVcJsonCredProcessor
from oid4vc.cred_processor import CredProcessors
from .status_handler import StatusHandler

from .config import Config
from .jwk import DID_JWK, P256
Expand Down Expand Up @@ -48,6 +49,8 @@ async def setup(context: InjectionContext):

context.injector.bind_instance(CredProcessors, processors)

status_handler = StatusHandler(context)
context.injector.bind_instance(StatusHandler, status_handler)

async def startup(profile: Profile, event: Event):
"""Startup event handler; start the OpenID4VCI server."""
Expand Down
6 changes: 5 additions & 1 deletion oid4vc/oid4vc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Config:
host: str
port: int
endpoint: str
status_handler: str

@classmethod
def from_settings(cls, settings: BaseSettings) -> "Config":
Expand All @@ -34,6 +35,9 @@ def from_settings(cls, settings: BaseSettings) -> "Config":
host = plugin_settings.get("host") or getenv("OID4VCI_HOST")
port = int(plugin_settings.get("port") or getenv("OID4VCI_PORT", "0"))
endpoint = plugin_settings.get("endpoint") or getenv("OID4VCI_ENDPOINT")
status_handler = plugin_settings.get("status_handler") or getenv(
"OID4VCI_STATUS_HANDLER"
)

if not host:
raise ConfigError("host", "OID4VCI_HOST")
Expand All @@ -42,4 +46,4 @@ def from_settings(cls, settings: BaseSettings) -> "Config":
if not endpoint:
raise ConfigError("endpoint", "OID4VCI_ENDPOINT")

return cls(host, port, endpoint)
return cls(host, port, endpoint, status_handler)
46 changes: 46 additions & 0 deletions oid4vc/oid4vc/status_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Status handler module."""

from acapy_agent.core.plugin_registry import PluginRegistry
from acapy_agent.admin.request_context import AdminRequestContext
from .config import Config


class StatusHandler:
"""Status handler class."""

def __init__(self, context: AdminRequestContext):
"""Initialize the StatusHandler class."""

self.context = context
self.handler = None

config = Config.from_settings(context.settings)
plugin_registry = context.inject_or(PluginRegistry)

# "status_list.v1_0.status_handler"
status_handler_path = config.status_handler

plugin_index = -1
for index, name in enumerate(plugin_registry.plugin_names):
if status_handler_path.startswith(name):
plugin_index = index
plugin_name = name
break

plugin_name = f"{plugin_name}." if plugin_name else ""
if plugin_index != -1:
self.handler = plugin_registry.plugins[plugin_index]
attributes = status_handler_path.removeprefix(plugin_name).split(".")
# Get handler object
for attribute in attributes:
self.handler = getattr(self.handler, attribute)

def assign_status_entries(
self, context: AdminRequestContext, supported_cred_id, exchange_id, status_type
):
"""Assign status entries."""

if self.handler:
return self.handler.assign_status_entries(
context, supported_cred_id, exchange_id, status_type
)
4 changes: 1 addition & 3 deletions oid4vc/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions status_list/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/python-3/.devcontainer/base.Dockerfile
ARG VARIANT="3.12"
FROM mcr.microsoft.com/devcontainers/python:${VARIANT}

ARG POETRY_VERSION="1.8.3"
ENV POETRY_HOME="/opt/poetry" \
POETRY_VERSION=${POETRY_VERSION}

RUN curl -sSL https://install.python-poetry.org | python3 - \
&& update-alternatives --install /usr/local/bin/poetry poetry /opt/poetry/bin/poetry 900 \
# Enable tab completion for bash
&& poetry completions bash >> /home/vscode/.bash_completion \
# Enable tab completion for Zsh
&& mkdir -p /home/vscode/.zfunc/ \
&& poetry completions zsh > /home/vscode/.zfunc/_poetry \
&& echo "fpath+=~/.zfunc\nautoload -Uz compinit && compinit" >> /home/vscode/.zshrc

COPY pyproject.toml ./
# COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.create true \
&& poetry install --no-root --no-interaction --with integration --extras "aca-py" \
&& rm -rf /root/.cache/pypoetry
59 changes: 59 additions & 0 deletions status_list/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "bitstring",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
"VARIANT": "3.12-bullseye",
"POETRY_VERSION": "1.8.3"
}
},
"customizations": {
"vscode": {
"extensions": ["ms-python.python", "ms-python.vscode-pylance"],
"settings": {
"python.testing.pytestArgs": ["./bitstring", "--no-cov"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.pytestPath": "pytest",
"editor.defaultFormatter": null,
"editor.formatOnSave": false, // enable per language
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
},
"editor.defaultFormatter": "charliermarsh.ruff",
"ruff.organizeImports": true
},
"ruff.codeAction.fixViolation": {
"enable": true
},
"ruff.fixAll": true,
"ruff.format.args": ["--config=./pyproject.toml"],
"ruff.lint.args": ["--config=./pyproject.toml"]
}
}
},

"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": false
}
},

// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",

"remoteEnv": {
"RUST_LOG": "aries-askar::log::target=error"
},

"mounts": [],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000, 3001],
"postCreateCommand": "bash ./.devcontainer/post-install.sh"
}
14 changes: 14 additions & 0 deletions status_list/.devcontainer/post-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
set -ex

# Convenience workspace directory for later use
WORKSPACE_DIR=$(pwd)

# install all ACA-Py requirements
python -m pip install --upgrade pip

# install black for formatting
pip3 install black

# Generate Poetry Lock file
poetry lock --no-update
18 changes: 18 additions & 0 deletions status_list/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.git
.pytest_cache
__pycache__
*.egg-info
build
docs
dist
test-reports
.python-version
docker
env
.venv
.devcontainer
.vscode
.vscode-sample
.pytest_cache
.ruff_cache
.pytest.ini
11 changes: 11 additions & 0 deletions status_list/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto

# Declare files that will always have LF line endings on checkout.
*.sh text eol=lf
*.md text eol=lf
*.json text eol=lf
*.conf text eol=lf
**/bin/* text eol=lf
scripts/* text eol=lf
demo/run_demo text eol=lf
Loading
Loading