kanidmd_lib/plugins/
oauth2.rs

1use compact_jwt::{crypto::JwsRs256Signer, JwsEs256Signer};
2use std::sync::Arc;
3
4use crate::event::{CreateEvent, ModifyEvent};
5use crate::plugins::Plugin;
6use crate::prelude::*;
7use crate::utils::password_from_random;
8
9pub struct OAuth2 {}
10
11impl Plugin for OAuth2 {
12    fn id() -> &'static str {
13        "plugin_oauth2"
14    }
15
16    #[instrument(level = "debug", name = "oauth2_pre_create_transform", skip_all)]
17    fn pre_create_transform(
18        qs: &mut QueryServerWriteTransaction,
19        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
20        _ce: &CreateEvent,
21    ) -> Result<(), OperationError> {
22        Self::modify_inner(qs, cand)
23    }
24
25    #[instrument(level = "debug", name = "oauth2_pre_modify", skip_all)]
26    fn pre_modify(
27        qs: &mut QueryServerWriteTransaction,
28        _pre_cand: &[Arc<EntrySealedCommitted>],
29        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
30        _me: &ModifyEvent,
31    ) -> Result<(), OperationError> {
32        Self::modify_inner(qs, cand)
33    }
34
35    #[instrument(level = "debug", name = "oauth2_pre_batch_modify", skip_all)]
36    fn pre_batch_modify(
37        qs: &mut QueryServerWriteTransaction,
38        _pre_cand: &[Arc<EntrySealedCommitted>],
39        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
40        _me: &BatchModifyEvent,
41    ) -> Result<(), OperationError> {
42        Self::modify_inner(qs, cand)
43    }
44}
45
46impl OAuth2 {
47    fn modify_inner<T: Clone>(
48        qs: &mut QueryServerWriteTransaction,
49        cand: &mut [Entry<EntryInvalid, T>],
50    ) -> Result<(), OperationError> {
51        let domain_level = qs.get_domain_version();
52
53        cand.iter_mut()
54            .filter(|entry| {
55                entry.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
56            })
57            .try_for_each(|entry| {
58                // Regenerate the basic secret, if needed
59                if entry.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerBasic.into()) &&
60                    !entry.attribute_pres(Attribute::OAuth2RsBasicSecret) {
61                        security_info!("regenerating oauth2 basic secret");
62                        let v = Value::SecretValue(password_from_random());
63                        entry.add_ava(Attribute::OAuth2RsBasicSecret, v);
64                }
65
66            let has_rs256 = entry.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable).unwrap_or(false);
67
68            if domain_level >= DOMAIN_LEVEL_10 {
69                debug!("Generating OAuth2 Key Object");
70                // OAuth2 now requires a KeyObject, configure it now.
71                entry.add_ava(Attribute::Class, EntryClass::KeyObject.to_value());
72                entry.add_ava(Attribute::Class, EntryClass::KeyObjectJwtEs256.to_value());
73                entry.add_ava(Attribute::Class, EntryClass::KeyObjectJweA128GCM.to_value());
74                if has_rs256 {
75                    entry.add_ava(Attribute::Class, EntryClass::KeyObjectJwtRs256.to_value());
76                }
77            } else {
78                if !entry.attribute_pres(Attribute::OAuth2RsTokenKey) {
79                    security_info!("regenerating oauth2 token key");
80                    let k = password_from_random();
81                    let v = Value::new_secret_str(&k);
82                    entry.add_ava(Attribute::OAuth2RsTokenKey, v);
83                }
84                if !entry.attribute_pres(Attribute::Es256PrivateKeyDer) {
85                    security_info!("regenerating oauth2 es256 private key");
86                    let der = JwsEs256Signer::generate_es256()
87                        .and_then(|jws| jws.private_key_to_der())
88                        .map_err(|e| {
89                            admin_error!(err = ?e, "Unable to generate ES256 JwsSigner private key");
90                            OperationError::CryptographyError
91                        })?;
92                    let v = Value::new_privatebinary(&der);
93                    entry.add_ava(Attribute::Es256PrivateKeyDer, v);
94                }
95                    if has_rs256 && !entry.attribute_pres(Attribute::Rs256PrivateKeyDer) {
96                    security_info!("regenerating oauth2 legacy rs256 private key");
97                    let der = JwsRs256Signer::generate_legacy_rs256()
98                        .and_then(|jws| jws.private_key_to_der())
99                        .map_err(|e| {
100                            admin_error!(err = ?e, "Unable to generate Legacy RS256 JwsSigner private key");
101                            OperationError::CryptographyError
102                        })?;
103                    let v = Value::new_privatebinary(&der);
104                    entry.add_ava(Attribute::Rs256PrivateKeyDer, v);
105                }
106            }
107
108            Ok(())
109        })
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use crate::prelude::*;
116
117    #[test]
118    fn test_pre_create_oauth2_secrets() {
119        let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
120
121        let uuid = Uuid::new_v4();
122        let e: Entry<EntryInit, EntryNew> = entry_init!(
123            (Attribute::Class, EntryClass::Object.to_value()),
124            (Attribute::Class, EntryClass::Account.to_value()),
125            (
126                Attribute::Class,
127                EntryClass::OAuth2ResourceServer.to_value()
128            ),
129            (
130                Attribute::Class,
131                EntryClass::OAuth2ResourceServerBasic.to_value()
132            ),
133            (Attribute::Uuid, Value::Uuid(uuid)),
134            (
135                Attribute::DisplayName,
136                Value::new_utf8s("test_resource_server")
137            ),
138            (Attribute::Name, Value::new_iname("test_resource_server")),
139            (
140                Attribute::OAuth2RsOriginLanding,
141                Value::new_url_s("https://demo.example.com").unwrap()
142            ),
143            (
144                Attribute::OAuth2RsScopeMap,
145                Value::new_oauthscopemap(
146                    UUID_IDM_ALL_ACCOUNTS,
147                    btreeset![OAUTH2_SCOPE_READ.to_string()]
148                )
149                .expect("invalid oauthscope")
150            )
151        );
152
153        let create = vec![e];
154
155        run_create_test!(
156            Ok(()),
157            preload,
158            create,
159            None,
160            |qs: &mut QueryServerWriteTransaction| {
161                let e = qs
162                    .internal_search_uuid(uuid)
163                    .expect("failed to get oauth2 config");
164                assert!(e.attribute_pres(Attribute::OAuth2RsBasicSecret));
165            }
166        );
167    }
168
169    #[test]
170    fn test_modify_oauth2_secrets_regenerate() {
171        let uuid = Uuid::new_v4();
172
173        let e: Entry<EntryInit, EntryNew> = entry_init!(
174            (Attribute::Class, EntryClass::Object.to_value()),
175            (Attribute::Class, EntryClass::Account.to_value()),
176            (
177                Attribute::Class,
178                EntryClass::OAuth2ResourceServer.to_value()
179            ),
180            (
181                Attribute::Class,
182                EntryClass::OAuth2ResourceServerBasic.to_value()
183            ),
184            (Attribute::Uuid, Value::Uuid(uuid)),
185            (Attribute::Name, Value::new_iname("test_resource_server")),
186            (
187                Attribute::DisplayName,
188                Value::new_utf8s("test_resource_server")
189            ),
190            (
191                Attribute::OAuth2RsOriginLanding,
192                Value::new_url_s("https://demo.example.com").unwrap()
193            ),
194            (
195                Attribute::OAuth2RsScopeMap,
196                Value::new_oauthscopemap(
197                    UUID_IDM_ALL_ACCOUNTS,
198                    btreeset![OAUTH2_SCOPE_READ.to_string()]
199                )
200                .expect("invalid oauthscope")
201            ),
202            (
203                Attribute::OAuth2RsBasicSecret,
204                Value::new_secret_str("12345")
205            )
206        );
207
208        let preload = vec![e];
209
210        run_modify_test!(
211            Ok(()),
212            preload,
213            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid))),
214            ModifyList::new_list(vec![
215                Modify::Purged(Attribute::OAuth2RsBasicSecret,),
216                Modify::Purged(Attribute::OAuth2RsTokenKey,)
217            ]),
218            None,
219            |_| {},
220            |qs: &mut QueryServerWriteTransaction| {
221                let e = qs
222                    .internal_search_uuid(uuid)
223                    .expect("failed to get oauth2 config");
224                assert!(e.attribute_pres(Attribute::OAuth2RsBasicSecret));
225                // Check the values are different.
226                assert!(e.get_ava_single_secret(Attribute::OAuth2RsBasicSecret) != Some("12345"));
227            }
228        );
229    }
230}