1use serde::Serialize;
2use serde_json::Value;
3
4use crate::config::AuthConfig;
5
6#[derive(Debug, Serialize)]
9pub struct SmartConfiguration {
10 #[serde(skip_serializing_if = "Option::is_none")]
11 pub issuer: Option<String>,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub jwks_uri: Option<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub authorization_endpoint: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub token_endpoint: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub introspection_endpoint: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub management_endpoint: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub registration_endpoint: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub revocation_endpoint: Option<String>,
26 pub scopes_supported: Vec<String>,
27 pub response_types_supported: Vec<String>,
28 pub grant_types_supported: Vec<String>,
29 pub token_endpoint_auth_methods_supported: Vec<String>,
30 pub code_challenge_methods_supported: Vec<String>,
31 pub token_endpoint_auth_signing_alg_values_supported: Vec<String>,
32 pub capabilities: Vec<String>,
33}
34
35impl SmartConfiguration {
36 pub fn from_config(config: &AuthConfig) -> Self {
38 let mut response_types_supported = vec!["token".to_string()];
39 let mut grant_types_supported = vec!["client_credentials".to_string()];
40
41 if config.smart_authorize_endpoint.is_some() {
42 response_types_supported.push("code".to_string());
43 grant_types_supported.push("authorization_code".to_string());
44 }
45
46 Self {
47 issuer: config.expected_issuer.clone(),
48 jwks_uri: config
49 .smart_jwks_url
50 .clone()
51 .or_else(|| config.jwks_url.clone()),
52 authorization_endpoint: config.smart_authorize_endpoint.clone(),
53 token_endpoint: config.smart_token_endpoint.clone(),
54 introspection_endpoint: config.smart_introspection_endpoint.clone(),
55 management_endpoint: config.smart_management_endpoint.clone(),
56 registration_endpoint: config.smart_registration_endpoint.clone(),
57 revocation_endpoint: config.smart_revocation_endpoint.clone(),
58 scopes_supported: vec![
59 "system/*.cruds".to_string(),
60 "system/*.rs".to_string(),
61 "system/*.r".to_string(),
62 ],
63 response_types_supported,
64 grant_types_supported,
65 token_endpoint_auth_methods_supported: vec!["private_key_jwt".to_string()],
66 code_challenge_methods_supported: vec!["S256".to_string()],
67 token_endpoint_auth_signing_alg_values_supported: vec![
68 "RS384".to_string(),
69 "ES384".to_string(),
70 ],
71 capabilities: vec![
72 "permission-v2".to_string(),
73 "client-confidential-asymmetric".to_string(),
74 ],
75 }
76 }
77
78 pub fn to_json(&self) -> Value {
80 serde_json::to_value(self).expect("SmartConfiguration serialization cannot fail")
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn test_smart_config_from_default() {
90 let config = AuthConfig::default();
91 let smart = SmartConfiguration::from_config(&config);
92
93 assert!(smart.issuer.is_none());
94 assert!(smart.token_endpoint.is_none());
95 assert!(smart.capabilities.contains(&"permission-v2".to_string()));
96 assert_eq!(smart.code_challenge_methods_supported, vec!["S256"]);
97 }
98
99 #[test]
100 fn test_smart_config_with_values() {
101 let config = AuthConfig {
102 expected_issuer: Some("https://idp.example.com".to_string()),
103 smart_token_endpoint: Some("https://idp.example.com/token".to_string()),
104 smart_authorize_endpoint: Some("https://idp.example.com/authorize".to_string()),
105 smart_jwks_url: Some("https://idp.example.com/.well-known/jwks.json".to_string()),
106 ..AuthConfig::default()
107 };
108 let smart = SmartConfiguration::from_config(&config);
109
110 assert_eq!(smart.issuer.as_deref(), Some("https://idp.example.com"));
111 assert_eq!(
112 smart.token_endpoint.as_deref(),
113 Some("https://idp.example.com/token")
114 );
115 assert!(
116 smart
117 .grant_types_supported
118 .contains(&"authorization_code".to_string())
119 );
120 assert!(smart.response_types_supported.contains(&"code".to_string()));
121 assert_eq!(
122 smart.jwks_uri.as_deref(),
123 Some("https://idp.example.com/.well-known/jwks.json")
124 );
125 }
126
127 #[test]
128 fn test_smart_config_json_serialization() {
129 let config = AuthConfig {
130 expected_issuer: Some("https://idp.example.com".to_string()),
131 ..AuthConfig::default()
132 };
133 let smart = SmartConfiguration::from_config(&config);
134 let json = smart.to_json();
135
136 assert!(json["capabilities"].is_array());
137 assert!(json["scopes_supported"].is_array());
138 assert_eq!(
139 json["code_challenge_methods_supported"],
140 serde_json::json!(["S256"])
141 );
142 assert!(json.get("authorization_endpoint").is_none());
144 }
145}