openid_client/types/
authorization_parameters.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// # AuthorizationParameters
7/// Values that will be sent with the authorize request
8#[derive(Debug, Default)]
9pub struct AuthorizationParameters {
10    /// [Auth Context Class Reference Values](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
11    pub acr_values: Option<Vec<String>>,
12    /// Audience of the Access Token
13    pub audience: Option<Vec<String>>,
14    /// Claims customization for `id_token` and `userinfo`
15    pub claims: Option<ClaimParam>,
16    /// Preferred [language script](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) for claims
17    pub claims_locales: Option<Vec<String>>,
18    /// [Client Id](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
19    pub client_id: Option<String>,
20    /// [PKCE code challenge method](https://datatracker.ietf.org/doc/html/rfc7636)
21    pub code_challenge_method: Option<String>,
22    /// [PKCE code challenge](https://datatracker.ietf.org/doc/html/rfc7636)
23    pub code_challenge: Option<String>,
24    /// [Display](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
25    pub display: Option<String>,
26    /// [Id token hint](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). Used for
27    /// hinting the user the authorization request is meant for.
28    pub id_token_hint: Option<String>,
29    /// [Login hint](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) for
30    /// the authorization server.
31    pub login_hint: Option<String>,
32    /// [Maximum Authentication Age](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
33    pub max_age: Option<String>,
34    /// [Nonce](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
35    pub nonce: Option<String>,
36    /// [Prompt Parameter](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
37    pub prompt: Option<Vec<String>>,
38    /// [Redirect Uri](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
39    /// to which response will be sent
40    pub redirect_uri: Option<String>,
41    /// Boolean value that marks if the client requesting for authorization is to be dynamically
42    /// registered
43    pub registration: Option<String>,
44    /// [Uri of the request object](https://www.rfc-editor.org/rfc/rfc9101#name-request-using-the-request_u)
45    pub request_uri: Option<String>,
46    /// [Request Object](https://www.rfc-editor.org/rfc/rfc9101#name-passing-a-request-object-by)
47    pub request: Option<String>,
48    /// [Resource Parameter](https://www.rfc-editor.org/rfc/rfc8693.html#section-2.1)
49    pub resource: Option<Vec<String>>,
50    /// [Response Mode](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
51    pub response_mode: Option<String>,
52    /// [Response Type](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
53    pub response_type: Option<Vec<String>>,
54    /// [Scope](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
55    pub scope: Option<Vec<String>>,
56    /// [State Parameter](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
57    pub state: Option<String>,
58    /// Preferred [language script](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) for UI
59    pub ui_locales: Option<Vec<String>>,
60    /// Other fields that will be sent with the authorization request
61    pub other: Option<HashMap<String, String>>,
62}
63
64impl From<AuthorizationParameters> for HashMap<String, String> {
65    fn from(val: AuthorizationParameters) -> Self {
66        let mut query = HashMap::new();
67
68        if let Some(other) = val.other {
69            for (k, v) in other {
70                query.entry(k).or_insert(v);
71            }
72        }
73
74        insert_query(&mut query, "client_id", val.client_id);
75
76        if let Some(acr_arr) = val.acr_values {
77            let mut acr_str = String::new();
78            for acr in acr_arr {
79                acr_str += &format!("{acr} ");
80            }
81
82            insert_query(
83                &mut query,
84                "acr_values",
85                Some(acr_str.trim_end().to_owned()),
86            );
87        }
88
89        if let Some(aud_arr) = val.audience {
90            let mut aud_str = String::new();
91            for aud in aud_arr {
92                aud_str += &format!("{aud} ");
93            }
94
95            insert_query(&mut query, "audience", Some(aud_str.trim_end().to_owned()));
96        }
97
98        if let Some(locale_arr) = val.claims_locales {
99            let mut locale_str = String::new();
100            for locale in locale_arr {
101                locale_str += &format!("{locale} ");
102            }
103
104            insert_query(
105                &mut query,
106                "claims_locales",
107                Some(locale_str.trim_end().to_owned()),
108            );
109        }
110        insert_query(
111            &mut query,
112            "code_challenge_method",
113            val.code_challenge_method,
114        );
115        insert_query(&mut query, "code_challenge", val.code_challenge);
116        insert_query(&mut query, "display", val.display);
117        insert_query(&mut query, "id_token_hint", val.id_token_hint);
118        insert_query(&mut query, "login_hint", val.login_hint);
119        insert_query(&mut query, "max_age", val.max_age);
120        insert_query(&mut query, "nonce", val.nonce);
121
122        if let Some(prompt_arr) = val.prompt {
123            let mut prompt_str = String::new();
124            for prompt in prompt_arr {
125                prompt_str += &format!("{prompt} ");
126            }
127
128            insert_query(&mut query, "prompt", Some(prompt_str.trim_end().to_owned()));
129        }
130
131        insert_query(&mut query, "redirect_uri", val.redirect_uri);
132        insert_query(&mut query, "registration", val.registration);
133        insert_query(&mut query, "request_uri", val.request_uri);
134        insert_query(&mut query, "request", val.request);
135        insert_query(&mut query, "response_mode", val.response_mode);
136
137        if let Some(res_arr) = val.response_type {
138            let mut res_str = String::new();
139            for res in res_arr {
140                res_str += &format!("{res} ");
141            }
142
143            insert_query(
144                &mut query,
145                "response_type",
146                Some(res_str.trim_end().to_owned()),
147            );
148        }
149
150        if let Some(scope_arr) = val.scope {
151            let mut scope_str = String::new();
152            for scope in scope_arr {
153                scope_str += &format!("{scope} ");
154            }
155
156            insert_query(&mut query, "scope", Some(scope_str.trim_end().to_owned()));
157        }
158
159        insert_query(&mut query, "state", val.state);
160
161        if let Some(ui_locales_arr) = val.ui_locales {
162            let mut ui_locales_str = String::new();
163            for ui_locale in ui_locales_arr {
164                ui_locales_str += &format!("{ui_locale} ");
165            }
166
167            insert_query(
168                &mut query,
169                "ui_locales",
170                Some(ui_locales_str.trim_end().to_owned()),
171            );
172        }
173
174        if let Some(c) = &val.claims {
175            if let Ok(s) = serde_json::to_string(c) {
176                query.insert("claims".to_owned(), s);
177            }
178        }
179
180        if let Some(resource) = &val.resource {
181            let mut resource_str = String::new();
182            for r in resource {
183                resource_str += &format!("{r} ");
184            }
185
186            insert_query(
187                &mut query,
188                "resource",
189                Some(resource_str.trim_end().to_owned()),
190            );
191        }
192
193        query
194    }
195}
196
197fn insert_query(qp: &mut HashMap<String, String>, key: &str, value: Option<String>) {
198    if let Some(v) = value {
199        qp.insert(key.to_owned(), v);
200    }
201}
202
203/// # ClaimParamValue
204/// Value for each [ClaimParam]
205#[derive(Serialize, Deserialize, Debug)]
206#[serde(untagged)]
207pub enum ClaimParamValue {
208    /// Null (null) value
209    Null,
210    /// See [ClaimsParameterMember]
211    ClaimParamMember(ClaimsParameterMember),
212}
213
214/// # ClaimParam
215/// The value of `claims` of [AuthorizationParameters]
216#[derive(Serialize, Deserialize, Debug, Default)]
217pub struct ClaimParam {
218    #[serde(skip_serializing_if = "Option::is_none")]
219    /// Claims structure of `id_token`
220    pub id_token: Option<HashMap<String, ClaimParamValue>>,
221    #[serde(skip_serializing_if = "Option::is_none", default)]
222    /// Claims structure of `userinfo` that will be returned
223    pub userinfo: Option<HashMap<String, ClaimParamValue>>,
224}
225
226/// # ClaimsParameterMember
227/// Customizing the claims from `claims` of [AuthorizationParameters]
228#[derive(Serialize, Deserialize, Debug, Default)]
229pub struct ClaimsParameterMember {
230    /// Marks as essential or not
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub essential: Option<bool>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    /// Claim that should be mapped to the specified key
235    pub value: Option<String>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    /// Claims that should be mapped to the specified key
238    pub values: Option<Vec<String>>,
239    #[serde(flatten)]
240    /// Other fields that should be sent
241    pub other: Option<HashMap<String, Value>>,
242}
243
244#[cfg(test)]
245mod claim_param_tests {
246    use std::collections::HashMap;
247
248    use assert_json_diff::assert_json_eq;
249    use serde_json::{json, Value};
250
251    use super::{ClaimParam, ClaimParamValue, ClaimsParameterMember};
252
253    #[test]
254    fn serialize_test_1() {
255        let mut userinfo: HashMap<String, ClaimParamValue> = HashMap::new();
256        userinfo.insert("null".to_string(), ClaimParamValue::Null);
257
258        let cpm = ClaimsParameterMember {
259            essential: Some(false),
260            value: None,
261            values: None,
262            other: None,
263        };
264
265        let cv = ClaimParamValue::ClaimParamMember(cpm);
266
267        userinfo.insert("cpm".to_string(), cv);
268
269        let claim_param = ClaimParam {
270            id_token: None,
271            userinfo: Some(userinfo),
272        };
273
274        let serialized_result = serde_json::to_string(&claim_param);
275
276        assert!(serialized_result.is_ok());
277
278        let string = serialized_result.unwrap();
279
280        assert_json_eq!(
281            json!({"userinfo": {"null": null,"cpm":{"essential": false}}}),
282            serde_json::from_str::<Value>(&string).unwrap()
283        );
284    }
285
286    #[test]
287    fn serialize_test_2() {
288        let mut userinfo: HashMap<String, ClaimParamValue> = HashMap::new();
289
290        let mut other: HashMap<String, Value> = HashMap::new();
291
292        other.insert(
293            "extra".to_string(),
294            json!({"this_is_obj": {"str":"hi", "bool": true}}),
295        );
296
297        let cpm = ClaimsParameterMember {
298            essential: None,
299            value: None,
300            values: Some(vec!["hello".to_string()]),
301            other: Some(other),
302        };
303
304        let cv = ClaimParamValue::ClaimParamMember(cpm);
305
306        userinfo.insert("cpm".to_string(), cv);
307
308        let claim_param = ClaimParam {
309            id_token: None,
310            userinfo: Some(userinfo),
311        };
312
313        let serialized_result = serde_json::to_string(&claim_param);
314
315        assert!(serialized_result.is_ok());
316
317        let string = serialized_result.unwrap();
318
319        assert_json_eq!(
320            json!({"userinfo": {"cpm":{"values": ["hello"], "extra" : {"this_is_obj": {"str": "hi", "bool": true}}}}}),
321            serde_json::from_str::<Value>(&string).unwrap()
322        );
323    }
324}