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 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 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 assert!(e.get_ava_single_secret(Attribute::OAuth2RsBasicSecret) != Some("12345"));
227 }
228 );
229 }
230}