feat(templates): enforce template usage, validate against schema#330
feat(templates): enforce template usage, validate against schema#330daniel-mader wants to merge 72 commits into
schema#330Conversation
… template schema Agent-Logs-Url: https://github.com/impierce/ssi-agent/sessions/0be5f674-cb23-4ded-9213-9316747a140f Co-authored-by: daniel-mader <45594838+daniel-mader@users.noreply.github.com>
Agent-Logs-Url: https://github.com/impierce/ssi-agent/sessions/0be5f674-cb23-4ded-9213-9316747a140f Co-authored-by: daniel-mader <45594838+daniel-mader@users.noreply.github.com>
Agent-Logs-Url: https://github.com/impierce/ssi-agent/sessions/0be5f674-cb23-4ded-9213-9316747a140f Co-authored-by: daniel-mader <45594838+daniel-mader@users.noreply.github.com>
…enforce-templates
Co-authored-by: Copilot <copilot@github.com>
- Extend PropertyAttribute with `immutable: bool` (default false) - Auto-populate `immutable: true` for all OpenBadges 3.0 schema properties - Enforce immutable properties cannot be removed during UpdateSchema - Ensure immutable flag cannot be altered via UpdateSchemaPropertiesAttributes - Add NonRemovablePropertyViolation error with HTTP 409 Conflict mapping - Add tests for immutable enforcement, auto-population, and flag preservation Agent-Logs-Url: https://github.com/impierce/ssi-agent/sessions/8907cf6d-663a-4880-9dd8-9699fbfa3e5b Co-authored-by: daniel-mader <45594838+daniel-mader@users.noreply.github.com>
…rcement Agent-Logs-Url: https://github.com/impierce/ssi-agent/sessions/8907cf6d-663a-4880-9dd8-9699fbfa3e5b Co-authored-by: daniel-mader <45594838+daniel-mader@users.noreply.github.com>
…al mapping - Define default required OB3 properties: achievement.name, achievement.criteria.narrative - Auto-merge these into the schema when creating an OpenBadges 3.0 template - Inject fixed achievement.type="Achievement" during credential mapping - Add map_open_badges_input_to_credential() to transform flat template input to nested OBv3 structure - Integrate mapping into credential creation endpoint for OB3 templates - Add unit tests for mapping and schema merging Agent-Logs-Url: https://github.com/impierce/ssi-agent/sessions/1932e429-576c-4b09-9988-fd8fc69574b0 Co-authored-by: daniel-mader <45594838+daniel-mader@users.noreply.github.com>
…dundant immutable assignment Agent-Logs-Url: https://github.com/impierce/ssi-agent/sessions/1932e429-576c-4b09-9988-fd8fc69574b0 Co-authored-by: daniel-mader <45594838+daniel-mader@users.noreply.github.com>
… into feat/enforce-templates
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
schema
| .type_url(type_url("templates#invalid-schema-properties-attributes")) | ||
| .source(self) | ||
| .finish(), | ||
| TemplateError::NonRemovablePropertyViolation(_) => ApiError::builder(StatusCode::CONFLICT) |
There was a problem hiding this comment.
These errors should be added to the problem details documentation. The type_url's should be "library#..." instead of "templates#".
| warn!("Template view not yet initialized; skipping credential configuration sync for `{template_id}`"); | ||
| return; | ||
| }; | ||
| match query_handler(template_id, view).await { |
There was a problem hiding this comment.
The match arm skips Draft and Deleted but implicitly allows Published and Archived. Should this be intended for Archived?
There was a problem hiding this comment.
We should indeed treat "Archived" as "read-only", but that has more implications. We're not actively supporting that at the moment, so we can leave it as is for now and properly cover it in an upcoming task. The main goal for now is to keep the credential configurations and template updates in sync.
| /// Panics if `schema` is not a JSON object. Callers must ensure the schema is an object | ||
| /// (or default to `{"type": "object"}`) before calling this function. | ||
| #[cfg(test)] | ||
| fn merge_open_badges_defaults(schema: &mut serde_json::Value) { |
There was a problem hiding this comment.
this test fn seems to only be called by test_merge_open_badges_defaults_into_empty_schema. but since the actual production code seems to be using validate_open_badges_required_properties as opposed to this auto-merge logic, I think this could be removed as it doesn't reflect the actual production code
There was a problem hiding this comment.
Edit: nvm the comment below, I figured out I had to update the status to published 👌
When I Create a new Template:
curl --location 'http://localhost:3033/v0/templates/create-template' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: {{API_KEY}}' \
--data '{
"title": "My Example Template",
"credentialFormat": "w3c_vc_data_model_v1-1",
"holderType": "organization"
}'
I assumed that a corresponding Credential Configuration would be created and included to the Credential Issuer Metadata, but when I hit:
curl --location 'http://localhost:3033/.well-known/openid-credential-issuer' \
--header 'X-API-KEY: {{API_KEY}}'
No new Credential Configuration (with title "My Example Template") is included in the Metadata. Is this expected, should I somehow trigger a Syncing command explicitly?
| DataModelUpdated, | ||
| CreatorUpdated, | ||
| HolderTypeUpdated, |
There was a problem hiding this comment.
The event types can now also be removed from ssi-agent/agent_event_publisher_http/README.md
| vec!["VerifiableCredential".to_string()] | ||
| } | ||
| DataModel::OpenBadges3_0 => { | ||
| vec!["OpenBadgeCredential".to_string(), "AchievementCredential".to_string()] |
There was a problem hiding this comment.
For OpenBadges, the type value should be an array where the first item must be "VerifiableCredential" and the second item must me either "OpenBadgeCredential" OR "AchievementCredential" (I think in most cases we default to "OpenBadgeCredential") (see: https://www.imsglobal.org/spec/ob/v3p0#achievementcredential)
| .type_url(type_url("templates#immutable-property-violation")) | ||
| .source(self) | ||
| .finish(), | ||
| TemplateError::DisallowedOpenBadgesProperties(_) => ApiError::builder(StatusCode::BAD_REQUEST) | ||
| .title("Disallowed OpenBadges Schema Properties") | ||
| .type_url(type_url("templates#disallowed-open-badges-properties")) | ||
| .source(self) | ||
| .finish(), | ||
| TemplateError::MissingRequiredOpenBadgesProperties(_) => ApiError::builder(StatusCode::BAD_REQUEST) | ||
| .title("Missing Required OpenBadges Schema Properties") | ||
| .type_url(type_url("templates#missing-required-open-badges-properties")) | ||
| .source(self) | ||
| .finish(), | ||
| TemplateError::InvalidRequiredPropertyType(_) => ApiError::builder(StatusCode::BAD_REQUEST) | ||
| .title("Invalid Required Property Type") | ||
| .type_url(type_url("templates#invalid-required-property-type")) | ||
| .source(self) | ||
| .finish(), | ||
| TemplateError::MissingTitle => ApiError::builder(StatusCode::BAD_REQUEST) | ||
| .title("Missing Title") | ||
| .type_url(type_url("templates#missing-title")) |
There was a problem hiding this comment.
All these type_urls are supposed to point to a specific Problem Details, but there is no description for them in the /docs/problem-details/library.md file
| pub credential: Value, | ||
| #[serde(default)] | ||
| pub is_signed: bool, | ||
| pub credential_configuration_id: String, |
There was a problem hiding this comment.
Not only the Credentials Endpoint includes the credential_configuration_id but also the Offers Endpoint includes the credential_configuration_ids property (in agent_api_http/src/v0/issuance/offers/mod.rs), which is only used for the JIT Credentials flow. I think for consistency it should be removed there as well (or for now add a TODO comment so we can address that at a later point).
|
Since this PR makes makes Credential Configurations unusable in the API, it also does not make sense anymore to initialize those 'default' Credential Configurations on startup, so imo |
| let path_elements: Vec<ClaimPathElement> = key | ||
| .split('.') | ||
| .map(|segment| ClaimPathElement::String(segment.to_string())) | ||
| .collect(); | ||
|
|
||
| let path = ClaimPathPointer::try_new(path_elements).ok()?; |
There was a problem hiding this comment.
If the provided properties look like this:
"properties": {
"age": {},
"name": {}
}then the claim path pointers will look like this:
["age"][name"]
but especially since this code is only executed when format == "vc+sd-jwt", it's more likely that the claim pat pointers end up as:
["credentialSubject", "age"]["credentialSubject", name"]
| let claims = if format == "vc+sd-jwt" { | ||
| build_claims_from_schema(template) | ||
| } else { | ||
| None | ||
| }; |
There was a problem hiding this comment.
Why only build claims when format == "vc+sd-jwt"? I think you can make it work for "dc+sd-jwt" as well if you rewrite this part to something like this:
let claims = if format == "vc+sd-jwt" {
build_claims_from_schema(template, "credentialSubject."])
} else if format == "dc+sd-jwt" {
build_claims_from_schema(template, "")
} else {
None
};where "credentialSubject." can be prepended to key to create the path_elements in fn build_claims_from_schema
related: https://github.com/impierce/ssi-agent/pull/330/changes#r3220225971
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Description of change
schema_properties_attributes, so they are locked and can never be altered/removed.BREAKING CHANGE
Require
templateIdwhen creating a credential. The two fieldsdata_modelandholder_typebecome immutable after the initial template creation.Links to any relevant issues
Be sure to reference any related issues by adding
fixes issue #.How the change has been tested
Describe the tests that you ran to verify your changes.
Make sure to provide instructions for the maintainer as well as any relevant configurations.
Definition of Done checklist
Add an
xto the boxes that are relevant to your changes.