Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1fd357b

Browse files
authoredJun 20, 2024··
feat: add /v1/configurations/credential_configurations endpoint (#83)
* feat: add Metadata `config.yml` file * test: fix "id_token" tests * style: rename `CredentialsSupportedCreated` to `CredentialConfigurationCreated` * feat: add `/v1/configurations/credential_configurations` endpoint * fix: ensure that batch credentials is still disabled * test: add `/v1/configurations/credential_configurations` to Postman collection * docs: update changelog * docs: deprecate `AGENT_ISSUANCE_CREDENTIAL_NAME` and `AGENT_ISSUANCE_CREDENTIAL_LOGO_URL` * feat: add Metadata `config.yml` file * test: fix "id_token" tests * chore: remove unused import * fix: minor improvements * chore: remove unnecessary `vec![]`
1 parent ab1c241 commit 1fd357b

File tree

18 files changed

+399
-129
lines changed

18 files changed

+399
-129
lines changed
 

‎.env.example

-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ AGENT_CONFIG_EVENT_STORE=postgres
33
AGENT_APPLICATION_URL=https://my-domain.example.org
44
AGENT_CONFIG_URL=https://my-domain.example.org
55
AGENT_APPLICATION_ENABLE_CORS=false
6-
AGENT_ISSUANCE_CREDENTIAL_NAME="Demo Credential"
7-
AGENT_ISSUANCE_CREDENTIAL_LOGO_URL=https://my-domain.example.org/credential_logo.png
86
AGENT_SECRET_MANAGER_STRONGHOLD_PATH="test.stronghold"
97
AGENT_SECRET_MANAGER_STRONGHOLD_PASSWORD="secure_password"
108
AGENT_SECRET_MANAGER_ISSUER_DID="did:key:z6Mkv5KkqNHuR6bPVT8fud3m9JaHBSEjEmiLp7HuGAwtbkk6"

‎CHANGELOG.md

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
### 20-16-2024
2+
Deprecated the following environment variables:
3+
* `AGENT_ISSUANCE_CREDENTIAL_NAME`
4+
* `AGENT_ISSUANCE_CREDENTIAL_LOGO_URL`
5+
6+
Both can now be dynamically configured through the `/v1/configurations/credential_configurations` endpoint. Example:
7+
```json
8+
// HTTP POST: /v1/configurations/credential_configurations
9+
{
10+
"display": [{
11+
"name": "Identity Credential", // <-- Credential Name
12+
"locale": "en",
13+
"logo": {
14+
"url": "https://impierce.com/images/logo-blue.png", // <-- Credential Logo URL
15+
"alt_text": "UniCore Logo"
16+
}
17+
}],
18+
"credentialConfigurationId": ...,
19+
"format": ...,
20+
"credential_definition": ...
21+
}
22+
```
23+
124
### 18-06-2024
225
Deprecated the following environment variables, which can now be configured in the `agent_application/config.yml` file:
326
* `AGENT_CONFIG_DEFAULT_DID_METHOD`: The first item in the `subject_syntax_types_supported` sequence will be used as the

‎agent_api_rest/postman/ssi-agent.postman_collection.json

+50
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,51 @@
66
"_exporter_id": "24972330"
77
},
88
"item": [
9+
{
10+
"name": "Configurations",
11+
"item": [
12+
{
13+
"name": "credential_configurations",
14+
"event": [
15+
{
16+
"listen": "test",
17+
"script": {
18+
"exec": [
19+
""
20+
],
21+
"type": "text/javascript",
22+
"packages": {}
23+
}
24+
}
25+
],
26+
"request": {
27+
"method": "POST",
28+
"header": [],
29+
"body": {
30+
"mode": "raw",
31+
"raw": "{\n \"credentialConfigurationId\":\"{{CREDENTIAL_CONFIGURATION_ID}}\",\n \"format\": \"jwt_vc_json\",\n \"credential_definition\": {\n \"type\": [\n \"VerifiableCredential\"\n ]\n },\n \"display\": [{\n \"name\": \"Identity Credential\",\n \"locale\": \"en\",\n \"logo\": {\n \"url\": \"https://impierce.com/images/logo-blue.png\",\n \"alt_text\": \"UniCore Logo\"\n }\n }]\n}",
32+
"options": {
33+
"raw": {
34+
"language": "json"
35+
}
36+
}
37+
},
38+
"url": {
39+
"raw": "{{HOST}}/v1/configurations/credential_configurations",
40+
"host": [
41+
"{{HOST}}"
42+
],
43+
"path": [
44+
"v1",
45+
"configurations",
46+
"credential_configurations"
47+
]
48+
}
49+
},
50+
"response": []
51+
}
52+
]
53+
},
954
{
1055
"name": "Issuance",
1156
"item": [
@@ -553,6 +598,11 @@
553598
"value": "http://192.168.1.127:3033",
554599
"type": "string"
555600
},
601+
{
602+
"key": "CREDENTIAL_CONFIGURATION_ID",
603+
"value": "identity_credential",
604+
"type": "string"
605+
},
556606
{
557607
"key": "PRE_AUTHORIZED_CODE",
558608
"value": "INITIAL_VALUE",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use agent_issuance::{
2+
server_config::{command::ServerConfigCommand, queries::ServerConfigView},
3+
state::{IssuanceState, SERVER_CONFIG_ID},
4+
};
5+
use agent_shared::handlers::{command_handler, query_handler};
6+
use axum::{
7+
extract::{Json, State},
8+
http::StatusCode,
9+
response::{IntoResponse, Response},
10+
};
11+
use oid4vci::{
12+
credential_format_profiles::{CredentialFormats, WithParameters},
13+
credential_issuer::credential_issuer_metadata::CredentialIssuerMetadata,
14+
};
15+
use serde::{Deserialize, Serialize};
16+
use serde_json::Value;
17+
use tracing::info;
18+
19+
#[derive(Deserialize, Serialize)]
20+
#[serde(rename_all = "camelCase")]
21+
pub struct CredentialConfigurationsEndpointRequest {
22+
pub credential_configuration_id: String,
23+
#[serde(flatten)]
24+
pub credential_format_with_parameters: CredentialFormats<WithParameters>,
25+
#[serde(default)]
26+
pub display: Vec<serde_json::Value>,
27+
}
28+
29+
#[axum_macros::debug_handler]
30+
pub(crate) async fn credential_configurations(
31+
State(state): State<IssuanceState>,
32+
Json(payload): Json<Value>,
33+
) -> Response {
34+
info!("Request Body: {}", payload);
35+
36+
let Ok(CredentialConfigurationsEndpointRequest {
37+
credential_configuration_id,
38+
credential_format_with_parameters,
39+
display,
40+
}) = serde_json::from_value(payload)
41+
else {
42+
return (StatusCode::BAD_REQUEST, "invalid payload").into_response();
43+
};
44+
45+
let command = ServerConfigCommand::AddCredentialConfiguration {
46+
credential_configuration_id,
47+
credential_format_with_parameters,
48+
display,
49+
};
50+
51+
if command_handler(SERVER_CONFIG_ID, &state.command.server_config, command)
52+
.await
53+
.is_err()
54+
{
55+
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
56+
}
57+
58+
match query_handler(SERVER_CONFIG_ID, &state.query.server_config).await {
59+
Ok(Some(ServerConfigView {
60+
credential_issuer_metadata:
61+
Some(CredentialIssuerMetadata {
62+
credential_configurations_supported,
63+
..
64+
}),
65+
..
66+
})) => (StatusCode::OK, Json(credential_configurations_supported)).into_response(),
67+
Ok(None) => StatusCode::NOT_FOUND.into_response(),
68+
_ => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
pub mod tests {
74+
use std::collections::HashMap;
75+
76+
use crate::{app, tests::BASE_URL};
77+
78+
use super::*;
79+
use agent_issuance::{startup_commands::startup_commands, state::initialize};
80+
use agent_shared::metadata::{load_metadata, set_metadata_configuration};
81+
use agent_store::in_memory;
82+
use agent_verification::services::test_utils::test_verification_services;
83+
use axum::{
84+
body::Body,
85+
http::{self, Request},
86+
Router,
87+
};
88+
use jsonwebtoken::Algorithm;
89+
use oid4vci::{
90+
credential_format_profiles::{w3c_verifiable_credentials::jwt_vc_json::CredentialDefinition, Parameters},
91+
credential_issuer::credential_configurations_supported::CredentialConfigurationsSupportedObject,
92+
proof::KeyProofMetadata,
93+
ProofType,
94+
};
95+
use serde_json::json;
96+
use tower::Service;
97+
98+
pub async fn credential_configurations(app: &mut Router) {
99+
let response = app
100+
.call(
101+
Request::builder()
102+
.method(http::Method::POST)
103+
.uri("/v1/configurations/credential_configurations")
104+
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
105+
.body(Body::from(
106+
serde_json::to_vec(&json!({
107+
"credentialConfigurationId": "badge",
108+
"format": "jwt_vc_json",
109+
"credential_definition": {
110+
"type": [
111+
"VerifiableCredential",
112+
"OpenBadgeCredential"
113+
]
114+
},
115+
"display": [{
116+
"name": "Badge",
117+
"logo": {
118+
"url": "https://example.com/logo.png",
119+
}
120+
}]
121+
}))
122+
.unwrap(),
123+
))
124+
.unwrap(),
125+
)
126+
.await
127+
.unwrap();
128+
129+
assert_eq!(response.status(), StatusCode::OK);
130+
assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json");
131+
132+
let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
133+
let credential_configurations_supported: HashMap<String, CredentialConfigurationsSupportedObject> =
134+
serde_json::from_slice(&body).unwrap();
135+
136+
assert_eq!(
137+
credential_configurations_supported,
138+
HashMap::from_iter([(
139+
"badge".to_string(),
140+
CredentialConfigurationsSupportedObject {
141+
credential_format: CredentialFormats::JwtVcJson(Parameters {
142+
parameters: (
143+
CredentialDefinition {
144+
type_: vec!["VerifiableCredential".to_string(), "OpenBadgeCredential".to_string()],
145+
credential_subject: Default::default(),
146+
},
147+
None,
148+
)
149+
.into(),
150+
}),
151+
cryptographic_binding_methods_supported: vec![
152+
"did:key".to_string(),
153+
"did:key".to_string(),
154+
"did:iota:rms".to_string(),
155+
"did:jwk".to_string(),
156+
],
157+
credential_signing_alg_values_supported: vec!["EdDSA".to_string()],
158+
proof_types_supported: HashMap::from_iter([(
159+
ProofType::Jwt,
160+
KeyProofMetadata {
161+
proof_signing_alg_values_supported: vec![Algorithm::EdDSA],
162+
},
163+
)]),
164+
display: vec![json!({
165+
"name": "Badge",
166+
"logo": {
167+
"url": "https://example.com/logo.png"
168+
}
169+
})],
170+
..Default::default()
171+
}
172+
)])
173+
);
174+
}
175+
176+
#[tokio::test]
177+
#[tracing_test::traced_test]
178+
async fn test_credential_configurations() {
179+
set_metadata_configuration("did:key");
180+
181+
let issuance_state = in_memory::issuance_state(Default::default()).await;
182+
183+
let verification_state = in_memory::verification_state(test_verification_services(), Default::default()).await;
184+
185+
initialize(&issuance_state, startup_commands(BASE_URL.clone(), &load_metadata())).await;
186+
187+
let mut app = app((issuance_state, verification_state));
188+
189+
credential_configurations(&mut app).await;
190+
}
191+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod credential_configurations;
2+
3+
pub(crate) use credential_configurations::credential_configurations;

‎agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ pub(crate) async fn openid_credential_issuer(State(state): State<IssuanceState>)
2525
mod tests {
2626
use std::collections::HashMap;
2727

28-
use crate::{app, tests::BASE_URL};
28+
use crate::{app, configurations::credential_configurations::tests::credential_configurations, tests::BASE_URL};
2929

3030
use super::*;
3131
use agent_issuance::{startup_commands::startup_commands, state::initialize};
3232
use agent_shared::{
33-
config,
3433
metadata::{load_metadata, set_metadata_configuration},
3534
UrlAppendHelpers,
3635
};
@@ -101,16 +100,16 @@ mod tests {
101100
"did:jwk".to_string(),
102101
],
103102
credential_signing_alg_values_supported: vec!["EdDSA".to_string()],
104-
proof_types_supported: HashMap::from_iter(vec![(
103+
proof_types_supported: HashMap::from_iter([(
105104
ProofType::Jwt,
106105
KeyProofMetadata {
107106
proof_signing_alg_values_supported: vec![Algorithm::EdDSA],
108107
},
109108
)]),
110109
display: vec![json!({
111-
"name": config!("credential_name", String).unwrap(),
110+
"name": "Badge",
112111
"logo": {
113-
"url": config!("credential_logo_url", String).unwrap()
112+
"url": "https://example.com/logo.png",
114113
}
115114
})],
116115
}
@@ -134,6 +133,8 @@ mod tests {
134133

135134
let mut app = app((issuance_state, verification_state));
136135

136+
credential_configurations(&mut app).await;
137+
137138
let _credential_issuer_metadata = openid_credential_issuer(&mut app).await;
138139
}
139140
}

‎agent_api_rest/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod configurations;
12
mod issuance;
23
mod verification;
34

@@ -43,6 +44,10 @@ pub fn app(state: ApplicationState) -> Router {
4344

4445
Router::new()
4546
// Agent Issuance Preparations
47+
.route(
48+
&path("/v1/configurations/credential_configurations"),
49+
post(configurations::credential_configurations),
50+
)
4651
.route(&path("/v1/credentials"), post(credentials))
4752
.route(&path("/v1/credentials/:credential_id"), get(get_credentials))
4853
.route(&path("/v1/offers"), post(offers))

‎agent_application/docker/README.md

+4-7
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ docker build -f docker/Dockerfile -t ssi-agent ..
1313
Inside the folder `/agent_application/docker`:
1414

1515
1. Inside `docker-compose.yml` replace the environment value: `AGENT_APPLICATION_URL` with your actual local IP address or URL (such as http://192.168.1.234:3033)
16-
2. Optionally, add the following environment variables:
17-
- `AGENT_ISSUANCE_CREDENTIAL_NAME`: To set the name of the credentials that will be issued.
18-
- `AGENT_ISSUANCE_CREDENTIAL_LOGO_URL`: To set the URL of the logo that will be used in the credentials.
1916
> [!IMPORTANT]
20-
> 3. By default, UniCore currently uses a default Stronghold file which is used for storing secrets. Using this default
17+
> 2. By default, UniCore currently uses a default Stronghold file which is used for storing secrets. Using this default
2118
> Stronghold is for testing purposes only and should not be used in production. To use your own Stronghold file, you
2219
> need to mount it in the `docker-compose.yml` file by replacing the default volume. Example:
2320
> ```yaml
@@ -33,15 +30,15 @@ Inside the folder `/agent_application/docker`:
3330
> is recommended to not change this environment variable.
3431
> - `AGENT_SECRET_MANAGER_STRONGHOLD_PASSWORD`: To set the password
3532
> - `AGENT_SECRET_MANAGER_ISSUER_KEY_ID`: To set the key id
36-
1. Optionally it is possible to configure an HTTP Event Publisher that can listen to certain events in `UniCore`
33+
3. Optionally it is possible to configure an HTTP Event Publisher that can listen to certain events in `UniCore`
3734
and publish them to a `target_url`. More information about the HTTP Event Publisher can be found [here](../../agent_event_publisher_http/README.md).
38-
2. To start the **SSI Agent**, a **Postgres** database along with **pgadmin** (Postgres Admin Interface) simply run:
35+
4. To start the **SSI Agent**, a **Postgres** database along with **pgadmin** (Postgres Admin Interface) simply run:
3936
4037
```bash
4138
docker compose up
4239
```
4340
44-
6. The REST API will be served at `http://0.0.0.0:3033`
41+
5. The REST API will be served at `http://0.0.0.0:3033`
4542
4643
> [!NOTE]
4744
> If you don't have rewrite rules enabled on your reverse proxy, you can set the `AGENT_CONFIG_BASE_PATH` to a value such as `ssi-agent`.

‎agent_event_publisher_http/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ CredentialResponseCreated
4646

4747
```
4848
ServerMetadataLoaded
49-
CredentialsSupportedCreated
49+
CredentialConfigurationAdded
5050
```
5151

5252
#### `authorization_request`
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.