Skip to content

Commit

Permalink
Add variable substitution for proof configs
Browse files Browse the repository at this point in the history
Signed-off-by: Lucas ONeil <[email protected]>
  • Loading branch information
loneil committed Sep 11, 2024
1 parent 7c8de01 commit acf50e2
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 4 deletions.
32 changes: 32 additions & 0 deletions oidc-controller/api/verificationConfigs/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from .variableSubstitutions import variable_substitution_map


def replace_proof_variables(proof_req_dict: dict) -> dict:
"""
Recursively replaces variables in the proof request with actual values.
The map is provided by imported variable_substitution_map.
Additional variables can be added to the map in the variableSubstitutions.py file,
or other dynamic functionality.
Args:
proof_req_dict (dict): The proof request dictionary from the resolved config.
Returns:
dict: The updated proof request dictionary with placeholder variables replaced.
"""

for k, v in proof_req_dict.items():
# If the value is a dictionary, recurse
if isinstance(v, dict):
replace_proof_variables(v)
# If the value is a list, iterate trhough list items and recurse
elif isinstance(v, list):
for i in v:
if isinstance(i, dict):
replace_proof_variables(i)
# If the value is a string and matches a key in the map, replace it
elif isinstance(v, str):
if v in variable_substitution_map:
proof_req_dict[k] = variable_substitution_map[v]()
# Base case: If the value is not a dict, list, or matching string, do nothing
return proof_req_dict
14 changes: 10 additions & 4 deletions oidc-controller/api/verificationConfigs/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from datetime import datetime
import time
from typing import Optional, List
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr

from .examples import ex_ver_config
from ..core.config import settings
from .helpers import replace_proof_variables


# Slightly modified from ACAPY models.
Expand Down Expand Up @@ -44,6 +46,9 @@ class VerificationConfigBase(BaseModel):
generate_consistent_identifier: Optional[bool] = Field(default=False)
include_v1_attributes: Optional[bool] = Field(default=False)

def get_now(self) -> int:
return int(time.time())

def generate_proof_request(self):
result = {
"name": "proof_requested",
Expand All @@ -61,15 +66,16 @@ def generate_proof_request(self):
"from": int(time.time()),
"to": int(time.time()),
}
# TODO add I indexing
for req_pred in self.proof_request.requested_predicates:
for i, req_pred in enumerate(self.proof_request.requested_predicates):
label = req_pred.label or "req_pred_" + str(i)
result["requested_predicates"][label] = req_pred.dict(exclude_none=True)
if settings.SET_NON_REVOKED:
result["requested_attributes"][label]["non_revoked"] = {
result["requested_predicates"][label]["non_revoked"] = {
"from": int(time.time()),
"to": int(time.time()),
}
# Recursively check for subistitution variables and invoke the apporpriate replacement function
result = replace_proof_variables(result)
return result

model_config = ConfigDict(json_schema_extra={"example": ex_ver_config})
Expand Down
77 changes: 77 additions & 0 deletions oidc-controller/api/verificationConfigs/variableSubstitutions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
This file contains the VariableSubstitutionMap class, which provides a mapping of static variables
that can be used in a proof. Other users of this project can add their own variable substitutions
or override the entire file to suit their own needs.
"""

from datetime import datetime, timedelta
import time
import re


class VariableSubstitutionMap:
def __init__(self):
# Map of static variables that can be used in a proof
# This class defines threshold_years_X as a dynamic one
self.static_map = {
"$now": self.get_now,
"$today_str": self.get_today_date,
"$tomorrow_str": self.get_tomorrow_date,
}

def get_threshold_years_date(self, years: int) -> int:
"""
Calculate the threshold date for a given number of years.
Args:
years (int): The number of years to subtract from the current year.
Returns:
int: The current date minux X years in YYYYMMDD format.
"""
threshold_date = datetime.today().replace(year=datetime.today().year - years)
return int(threshold_date.strftime("%Y%m%d"))

def get_now(self) -> int:
"""
Get the current timestamp.
Returns:
int: The current timestamp in seconds since the epoch.
"""
return int(time.time())

def get_today_date(self) -> str:
"""
Get today's date in YYYYMMDD format.
Returns:
str: Today's date in YYYYMMDD format.
"""
return datetime.today().strftime("%Y%m%d")

def get_tomorrow_date(self) -> str:
"""
Get tomorrow's date in YYYYMMDD format.
Returns:
str: Tomorrow's date in YYYYMMDD format.
"""
return (datetime.today() + timedelta(days=1)).strftime("%Y%m%d")

# For "dynamic" variables, we use a regex to match the key and return a lambda function
# So a proof request can use $threshold_years_X to get the threshold birthdate for X years
def __contains__(self, key: str) -> bool:
return key in self.static_map or re.match(r"\$threshold_years_(\d+)", key)

def __getitem__(self, key: str):
if key in self.static_map:
return self.static_map[key]
match = re.match(r"\$threshold_years_(\d+)", key)
if match:
return lambda: self.get_threshold_years_date(int(match.group(1)))
raise KeyError(f"Key {key} not found in format_args_function_map")


# Create an instance of the custom mapping class
variable_substitution_map = VariableSubstitutionMap()

0 comments on commit acf50e2

Please sign in to comment.