1mod link;
12mod provider;
13mod session;
14
15pub use self::{
16 link::PgUpstreamOAuthLinkRepository, provider::PgUpstreamOAuthProviderRepository,
17 session::PgUpstreamOAuthSessionRepository,
18};
19
20#[cfg(test)]
21mod tests {
22 use chrono::Duration;
23 use mas_data_model::{
24 UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderOnBackchannelLogout,
25 UpstreamOAuthProviderTokenAuthMethod, clock::MockClock,
26 };
27 use mas_iana::jose::JsonWebSignatureAlg;
28 use mas_storage::{
29 Pagination, RepositoryAccess,
30 upstream_oauth2::{
31 UpstreamOAuthLinkFilter, UpstreamOAuthLinkRepository, UpstreamOAuthProviderFilter,
32 UpstreamOAuthProviderParams, UpstreamOAuthProviderRepository,
33 UpstreamOAuthSessionFilter, UpstreamOAuthSessionRepository,
34 },
35 user::UserRepository,
36 };
37 use oauth2_types::scope::{OPENID, Scope};
38 use rand::SeedableRng;
39 use sqlx::PgPool;
40
41 use crate::PgRepository;
42
43 #[sqlx::test(migrator = "crate::MIGRATOR")]
44 async fn test_repository(pool: PgPool) {
45 let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42);
46 let clock = MockClock::default();
47 let mut repo = PgRepository::from_pool(&pool).await.unwrap();
48
49 let all_providers = repo.upstream_oauth_provider().all_enabled().await.unwrap();
51 assert_eq!(all_providers, []);
52
53 let provider = repo
55 .upstream_oauth_provider()
56 .add(
57 &mut rng,
58 &clock,
59 UpstreamOAuthProviderParams {
60 issuer: Some("https://example.com/".to_owned()),
61 human_name: None,
62 brand_name: None,
63 scope: Scope::from_iter([OPENID]),
64 token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None,
65 id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
66 fetch_userinfo: false,
67 userinfo_signed_response_alg: None,
68 token_endpoint_signing_alg: None,
69 client_id: "client-id".to_owned(),
70 encrypted_client_secret: None,
71 claims_imports: UpstreamOAuthProviderClaimsImports::default(),
72 token_endpoint_override: None,
73 authorization_endpoint_override: None,
74 userinfo_endpoint_override: None,
75 jwks_uri_override: None,
76 discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
77 pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
78 response_mode: None,
79 additional_authorization_parameters: Vec::new(),
80 forward_login_hint: false,
81 ui_order: 0,
82 on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
83 registration_token_required: false,
84 },
85 )
86 .await
87 .unwrap();
88
89 let provider = repo
91 .upstream_oauth_provider()
92 .lookup(provider.id)
93 .await
94 .unwrap()
95 .expect("provider to be found in the database");
96 assert_eq!(provider.issuer.as_deref(), Some("https://example.com/"));
97 assert_eq!(provider.client_id, "client-id");
98
99 let providers = repo.upstream_oauth_provider().all_enabled().await.unwrap();
101 assert_eq!(providers.len(), 1);
102 assert_eq!(providers[0].issuer.as_deref(), Some("https://example.com/"));
103 assert_eq!(providers[0].client_id, "client-id");
104
105 let session = repo
107 .upstream_oauth_session()
108 .add(
109 &mut rng,
110 &clock,
111 &provider,
112 "some-state".to_owned(),
113 None,
114 Some("some-nonce".to_owned()),
115 )
116 .await
117 .unwrap();
118
119 let session = repo
121 .upstream_oauth_session()
122 .lookup(session.id)
123 .await
124 .unwrap()
125 .expect("session to be found in the database");
126 assert_eq!(session.provider_id, provider.id);
127 assert_eq!(session.link_id(), None);
128 assert!(session.is_pending());
129 assert!(!session.is_completed());
130 assert!(!session.is_consumed());
131
132 let link = repo
134 .upstream_oauth_link()
135 .add(
136 &mut rng,
137 &clock,
138 &provider,
139 "a-subject".to_owned(),
140 Some("alice@example.com".to_owned()),
141 )
142 .await
143 .unwrap();
144
145 repo.upstream_oauth_link()
147 .lookup(link.id)
148 .await
149 .unwrap()
150 .expect("link to be found in database");
151
152 let link = repo
154 .upstream_oauth_link()
155 .find_by_subject(&provider, "a-subject")
156 .await
157 .unwrap()
158 .expect("link to be found in database");
159 assert_eq!(link.subject, "a-subject");
160 assert_eq!(link.provider_id, provider.id);
161
162 let session = repo
163 .upstream_oauth_session()
164 .complete_with_link(&clock, session, &link, None, None, None, None)
165 .await
166 .unwrap();
167 let session = repo
169 .upstream_oauth_session()
170 .lookup(session.id)
171 .await
172 .unwrap()
173 .expect("session to be found in the database");
174 assert!(session.is_completed());
175 assert!(!session.is_consumed());
176 assert_eq!(session.link_id(), Some(link.id));
177
178 let user = repo
180 .user()
181 .add(&mut rng, &clock, "john".to_owned())
182 .await
183 .unwrap();
184 let browser_session = repo
185 .browser_session()
186 .add(&mut rng, &clock, &user, None)
187 .await
188 .unwrap();
189
190 let session = repo
191 .upstream_oauth_session()
192 .consume(&clock, session, &browser_session)
193 .await
194 .unwrap();
195
196 let session = repo
198 .upstream_oauth_session()
199 .lookup(session.id)
200 .await
201 .unwrap()
202 .expect("session to be found in the database");
203 assert!(session.is_consumed());
204
205 repo.upstream_oauth_link()
206 .associate_to_user(&link, &user)
207 .await
208 .unwrap();
209
210 let filter = UpstreamOAuthLinkFilter::new()
212 .for_user(&user)
213 .for_provider(&provider)
214 .for_subject("a-subject")
215 .enabled_providers_only();
216
217 let links = repo
218 .upstream_oauth_link()
219 .list(filter, Pagination::first(10))
220 .await
221 .unwrap();
222 assert!(!links.has_previous_page);
223 assert!(!links.has_next_page);
224 assert_eq!(links.edges.len(), 1);
225 assert_eq!(links.edges[0].node.id, link.id);
226 assert_eq!(links.edges[0].node.user_id, Some(user.id));
227
228 assert_eq!(repo.upstream_oauth_link().count(filter).await.unwrap(), 1);
229
230 let matching_filter = UpstreamOAuthLinkFilter::new().matching_human_account_name("LICE");
232 let links = repo
233 .upstream_oauth_link()
234 .list(matching_filter, Pagination::first(10))
235 .await
236 .unwrap();
237 assert_eq!(links.edges.len(), 1);
238 assert_eq!(links.edges[0].node.id, link.id);
239 assert_eq!(
240 repo.upstream_oauth_link()
241 .count(matching_filter)
242 .await
243 .unwrap(),
244 1
245 );
246
247 let non_matching_filter =
249 UpstreamOAuthLinkFilter::new().matching_human_account_name("nope");
250 let links = repo
251 .upstream_oauth_link()
252 .list(non_matching_filter, Pagination::first(10))
253 .await
254 .unwrap();
255 assert_eq!(links.edges.len(), 0);
256 assert_eq!(
257 repo.upstream_oauth_link()
258 .count(non_matching_filter)
259 .await
260 .unwrap(),
261 0
262 );
263
264 assert_eq!(
266 repo.upstream_oauth_provider()
267 .count(UpstreamOAuthProviderFilter::new())
268 .await
269 .unwrap(),
270 1
271 );
272 assert_eq!(
273 repo.upstream_oauth_provider()
274 .count(UpstreamOAuthProviderFilter::new().enabled_only())
275 .await
276 .unwrap(),
277 1
278 );
279 assert_eq!(
280 repo.upstream_oauth_provider()
281 .count(UpstreamOAuthProviderFilter::new().disabled_only())
282 .await
283 .unwrap(),
284 0
285 );
286
287 repo.upstream_oauth_provider()
289 .disable(&clock, provider.clone())
290 .await
291 .unwrap();
292
293 assert_eq!(
295 repo.upstream_oauth_provider()
296 .count(UpstreamOAuthProviderFilter::new())
297 .await
298 .unwrap(),
299 1
300 );
301 assert_eq!(
302 repo.upstream_oauth_provider()
303 .count(UpstreamOAuthProviderFilter::new().enabled_only())
304 .await
305 .unwrap(),
306 0
307 );
308 assert_eq!(
309 repo.upstream_oauth_provider()
310 .count(UpstreamOAuthProviderFilter::new().disabled_only())
311 .await
312 .unwrap(),
313 1
314 );
315
316 let session_filter = UpstreamOAuthSessionFilter::new().for_provider(&provider);
318
319 let session_count = repo
321 .upstream_oauth_session()
322 .count(session_filter)
323 .await
324 .unwrap();
325 assert_eq!(session_count, 1);
326
327 let session_page = repo
329 .upstream_oauth_session()
330 .list(session_filter, Pagination::first(10))
331 .await
332 .unwrap();
333
334 assert_eq!(session_page.edges.len(), 1);
335 assert_eq!(session_page.edges[0].node.id, session.id);
336 assert!(!session_page.has_next_page);
337 assert!(!session_page.has_previous_page);
338
339 repo.upstream_oauth_provider()
341 .delete(provider)
342 .await
343 .unwrap();
344 assert_eq!(
345 repo.upstream_oauth_provider()
346 .count(UpstreamOAuthProviderFilter::new())
347 .await
348 .unwrap(),
349 0
350 );
351 }
352
353 #[sqlx::test(migrator = "crate::MIGRATOR")]
356 async fn test_provider_repository_pagination(pool: PgPool) {
357 let scope = Scope::from_iter([OPENID]);
358
359 let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42);
360 let clock = MockClock::default();
361 let mut repo = PgRepository::from_pool(&pool).await.unwrap();
362
363 let filter = UpstreamOAuthProviderFilter::new();
364
365 assert_eq!(
367 repo.upstream_oauth_provider().count(filter).await.unwrap(),
368 0
369 );
370
371 let mut ids = Vec::with_capacity(20);
372 for idx in 0..20 {
374 let client_id = format!("client-{idx}");
375 let provider = repo
376 .upstream_oauth_provider()
377 .add(
378 &mut rng,
379 &clock,
380 UpstreamOAuthProviderParams {
381 issuer: None,
382 human_name: None,
383 brand_name: None,
384 scope: scope.clone(),
385 token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None,
386 fetch_userinfo: false,
387 userinfo_signed_response_alg: None,
388 token_endpoint_signing_alg: None,
389 id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
390 client_id,
391 encrypted_client_secret: None,
392 claims_imports: UpstreamOAuthProviderClaimsImports::default(),
393 token_endpoint_override: None,
394 authorization_endpoint_override: None,
395 userinfo_endpoint_override: None,
396 jwks_uri_override: None,
397 discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
398 pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
399 response_mode: None,
400 additional_authorization_parameters: Vec::new(),
401 forward_login_hint: false,
402 ui_order: 0,
403 on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
404 registration_token_required: false,
405 },
406 )
407 .await
408 .unwrap();
409 ids.push(provider.id);
410 clock.advance(Duration::microseconds(10 * 1000 * 1000));
411 }
412
413 assert_eq!(
415 repo.upstream_oauth_provider().count(filter).await.unwrap(),
416 20
417 );
418
419 let page = repo
421 .upstream_oauth_provider()
422 .list(filter, Pagination::first(10))
423 .await
424 .unwrap();
425
426 assert!(page.has_next_page);
428 let edge_ids: Vec<_> = page.edges.iter().map(|p| p.node.id).collect();
429 assert_eq!(&edge_ids, &ids[..10]);
430
431 let other_page = repo
434 .upstream_oauth_provider()
435 .list(filter.enabled_only(), Pagination::first(10))
436 .await
437 .unwrap();
438
439 assert_eq!(page, other_page);
440
441 let page = repo
443 .upstream_oauth_provider()
444 .list(filter, Pagination::first(10).after(ids[9]))
445 .await
446 .unwrap();
447
448 assert!(!page.has_next_page);
450 let edge_ids: Vec<_> = page.edges.iter().map(|p| p.node.id).collect();
451 assert_eq!(&edge_ids, &ids[10..]);
452
453 let page = repo
455 .upstream_oauth_provider()
456 .list(filter, Pagination::last(10))
457 .await
458 .unwrap();
459
460 assert!(page.has_previous_page);
462 let edge_ids: Vec<_> = page.edges.iter().map(|p| p.node.id).collect();
463 assert_eq!(&edge_ids, &ids[10..]);
464
465 let page = repo
467 .upstream_oauth_provider()
468 .list(filter, Pagination::last(10).before(ids[10]))
469 .await
470 .unwrap();
471
472 assert!(!page.has_previous_page);
474 let edge_ids: Vec<_> = page.edges.iter().map(|p| p.node.id).collect();
475 assert_eq!(&edge_ids, &ids[..10]);
476
477 let page = repo
479 .upstream_oauth_provider()
480 .list(filter, Pagination::first(10).after(ids[5]).before(ids[8]))
481 .await
482 .unwrap();
483
484 assert!(!page.has_next_page);
486 let edge_ids: Vec<_> = page.edges.iter().map(|p| p.node.id).collect();
487 assert_eq!(&edge_ids, &ids[6..8]);
488
489 assert_eq!(
491 repo.upstream_oauth_provider()
492 .list(
493 UpstreamOAuthProviderFilter::new().disabled_only(),
494 Pagination::first(1)
495 )
496 .await
497 .unwrap()
498 .edges,
499 []
500 );
501 }
502
503 #[sqlx::test(migrator = "crate::MIGRATOR")]
506 async fn test_session_repository_pagination(pool: PgPool) {
507 let scope = Scope::from_iter([OPENID]);
508
509 let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42);
510 let clock = MockClock::default();
511 let mut repo = PgRepository::from_pool(&pool).await.unwrap();
512
513 let provider = repo
515 .upstream_oauth_provider()
516 .add(
517 &mut rng,
518 &clock,
519 UpstreamOAuthProviderParams {
520 issuer: Some("https://example.com/".to_owned()),
521 human_name: None,
522 brand_name: None,
523 scope,
524 token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None,
525 id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
526 fetch_userinfo: false,
527 userinfo_signed_response_alg: None,
528 token_endpoint_signing_alg: None,
529 client_id: "client-id".to_owned(),
530 encrypted_client_secret: None,
531 claims_imports: UpstreamOAuthProviderClaimsImports::default(),
532 token_endpoint_override: None,
533 authorization_endpoint_override: None,
534 userinfo_endpoint_override: None,
535 jwks_uri_override: None,
536 discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
537 pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
538 response_mode: None,
539 additional_authorization_parameters: Vec::new(),
540 forward_login_hint: false,
541 ui_order: 0,
542 on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
543 registration_token_required: false,
544 },
545 )
546 .await
547 .unwrap();
548
549 let filter = UpstreamOAuthSessionFilter::new().for_provider(&provider);
550
551 assert_eq!(
553 repo.upstream_oauth_session().count(filter).await.unwrap(),
554 0
555 );
556
557 let mut links = Vec::with_capacity(3);
558 for subject in ["alice", "bob", "charlie"] {
559 let link = repo
560 .upstream_oauth_link()
561 .add(&mut rng, &clock, &provider, subject.to_owned(), None)
562 .await
563 .unwrap();
564 links.push(link);
565 }
566
567 let mut ids = Vec::with_capacity(20);
568 let sids = ["one", "two"].into_iter().cycle();
569 for (idx, (link, sid)) in links.iter().cycle().zip(sids).enumerate().take(20) {
571 let state = format!("state-{idx}");
572 let session = repo
573 .upstream_oauth_session()
574 .add(&mut rng, &clock, &provider, state, None, None)
575 .await
576 .unwrap();
577 let id_token_claims = serde_json::json!({
578 "sub": link.subject,
579 "sid": sid,
580 "aud": provider.client_id,
581 "iss": "https://example.com/",
582 });
583 let session = repo
584 .upstream_oauth_session()
585 .complete_with_link(
586 &clock,
587 session,
588 link,
589 None,
590 Some(id_token_claims),
591 None,
592 None,
593 )
594 .await
595 .unwrap();
596 ids.push(session.id);
597 clock.advance(Duration::microseconds(10 * 1000 * 1000));
598 }
599
600 assert_eq!(
602 repo.upstream_oauth_session().count(filter).await.unwrap(),
603 20
604 );
605
606 let page = repo
608 .upstream_oauth_session()
609 .list(filter, Pagination::first(10))
610 .await
611 .unwrap();
612
613 assert!(page.has_next_page);
615 let edge_ids: Vec<_> = page.edges.iter().map(|s| s.node.id).collect();
616 assert_eq!(&edge_ids, &ids[..10]);
617
618 let page = repo
620 .upstream_oauth_session()
621 .list(filter, Pagination::first(10).after(ids[9]))
622 .await
623 .unwrap();
624
625 assert!(!page.has_next_page);
627 let edge_ids: Vec<_> = page.edges.iter().map(|s| s.node.id).collect();
628 assert_eq!(&edge_ids, &ids[10..]);
629
630 let page = repo
632 .upstream_oauth_session()
633 .list(filter, Pagination::last(10))
634 .await
635 .unwrap();
636
637 assert!(page.has_previous_page);
639 let edge_ids: Vec<_> = page.edges.iter().map(|s| s.node.id).collect();
640 assert_eq!(&edge_ids, &ids[10..]);
641
642 let page = repo
644 .upstream_oauth_session()
645 .list(filter, Pagination::last(10).before(ids[10]))
646 .await
647 .unwrap();
648
649 assert!(!page.has_previous_page);
651 let edge_ids: Vec<_> = page.edges.iter().map(|s| s.node.id).collect();
652 assert_eq!(&edge_ids, &ids[..10]);
653
654 let page = repo
656 .upstream_oauth_session()
657 .list(filter, Pagination::first(10).after(ids[5]).before(ids[11]))
658 .await
659 .unwrap();
660
661 assert!(!page.has_next_page);
663 let edge_ids: Vec<_> = page.edges.iter().map(|s| s.node.id).collect();
664 assert_eq!(&edge_ids, &ids[6..11]);
665
666 assert_eq!(
668 repo.upstream_oauth_session()
669 .count(filter.with_sub_claim("alice").with_sid_claim("one"))
670 .await
671 .unwrap(),
672 4
673 );
674 assert_eq!(
675 repo.upstream_oauth_session()
676 .count(filter.with_sub_claim("bob").with_sid_claim("two"))
677 .await
678 .unwrap(),
679 4
680 );
681
682 let page = repo
683 .upstream_oauth_session()
684 .list(
685 filter.with_sub_claim("alice").with_sid_claim("one"),
686 Pagination::first(10),
687 )
688 .await
689 .unwrap();
690 assert_eq!(page.edges.len(), 4);
691 for edge in page.edges {
692 assert_eq!(
693 edge.node
694 .id_token_claims()
695 .unwrap()
696 .get("sub")
697 .unwrap()
698 .as_str(),
699 Some("alice")
700 );
701 assert_eq!(
702 edge.node
703 .id_token_claims()
704 .unwrap()
705 .get("sid")
706 .unwrap()
707 .as_str(),
708 Some("one")
709 );
710 }
711 }
712}