@@ -10,11 +10,11 @@ use camino::Utf8PathBuf;
10
10
use mas_iana:: jose:: JsonWebSignatureAlg ;
11
11
use schemars:: JsonSchema ;
12
12
use serde:: { Deserialize , Serialize , de:: Error } ;
13
- use serde_with:: skip_serializing_none;
13
+ use serde_with:: { serde_as , skip_serializing_none} ;
14
14
use ulid:: Ulid ;
15
15
use url:: Url ;
16
16
17
- use crate :: ConfigurationSection ;
17
+ use crate :: { ConfigurationSection , ClientSecret , ClientSecretRaw } ;
18
18
19
19
/// Upstream OAuth 2.0 providers configuration
20
20
#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema , Default ) ]
@@ -475,6 +475,7 @@ impl OnBackchannelLogout {
475
475
}
476
476
477
477
/// Configuration for one upstream OAuth 2 provider.
478
+ #[ serde_as]
478
479
#[ skip_serializing_none]
479
480
#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
480
481
pub struct Provider {
@@ -541,8 +542,10 @@ pub struct Provider {
541
542
///
542
543
/// Used by the `client_secret_basic`, `client_secret_post`, and
543
544
/// `client_secret_jwt` methods
544
- #[ serde( skip_serializing_if = "Option::is_none" ) ]
545
- pub client_secret : Option < String > ,
545
+ #[ schemars( with = "ClientSecretRaw" ) ]
546
+ #[ serde_as( as = "serde_with::TryFromInto<ClientSecretRaw>" ) ]
547
+ #[ serde( flatten) ]
548
+ pub client_secret : Option < ClientSecret > ,
546
549
547
550
/// The method to authenticate the client with the provider
548
551
pub token_endpoint_auth_method : TokenAuthMethod ,
@@ -656,3 +659,110 @@ pub struct Provider {
656
659
#[ serde( default , skip_serializing_if = "OnBackchannelLogout::is_default" ) ]
657
660
pub on_backchannel_logout : OnBackchannelLogout ,
658
661
}
662
+
663
+ impl Provider {
664
+ /// Returns the client secret.
665
+ ///
666
+ /// If `client_secret_file` was given, the secret is read from that file.
667
+ ///
668
+ /// # Errors
669
+ ///
670
+ /// Returns an error when the client secret could not be read from file.
671
+ pub async fn client_secret ( & self ) -> anyhow:: Result < Option < String > > {
672
+ Ok ( match & self . client_secret {
673
+ Some ( client_secret) => Some ( client_secret. value ( ) . await ?) ,
674
+ None => None ,
675
+ } )
676
+ }
677
+ }
678
+
679
+ #[ cfg( test) ]
680
+ mod tests {
681
+ use std:: str:: FromStr ;
682
+
683
+ use figment:: {
684
+ Figment , Jail ,
685
+ providers:: { Format , Yaml } ,
686
+ } ;
687
+ use tokio:: { runtime:: Handle , task} ;
688
+
689
+ use super :: * ;
690
+
691
+ #[ tokio:: test]
692
+ async fn load_config ( ) {
693
+ task:: spawn_blocking ( || {
694
+ Jail :: expect_with ( |jail| {
695
+ jail. create_file (
696
+ "config.yaml" ,
697
+ r#"
698
+ upstream_oauth2:
699
+ providers:
700
+ - id: 01GFWR28C4KNE04WG3HKXB7C9R
701
+ client_id: upstream-oauth2
702
+ token_endpoint_auth_method: none
703
+
704
+ - id: 01GFWR32NCQ12B8Z0J8CPXRRB6
705
+ client_id: upstream-oauth2
706
+ client_secret_file: secret
707
+ token_endpoint_auth_method: client_secret_basic
708
+
709
+ - id: 01GFWR3WHR93Y5HK389H28VHZ9
710
+ client_id: upstream-oauth2
711
+ client_secret: c1!3n753c237
712
+ token_endpoint_auth_method: client_secret_post
713
+
714
+ - id: 01GFWR43R2ZZ8HX9CVBNW9TJWG
715
+ client_id: upstream-oauth2
716
+ client_secret_file: secret
717
+ token_endpoint_auth_method: client_secret_jwt
718
+
719
+ - id: 01GFWR4BNFDCC4QDG6AMSP1VRR
720
+ client_id: upstream-oauth2
721
+ token_endpoint_auth_method: private_key_jwt
722
+ jwks:
723
+ keys:
724
+ - kid: "03e84aed4ef4431014e8617567864c4efaaaede9"
725
+ kty: "RSA"
726
+ alg: "RS256"
727
+ use: "sig"
728
+ e: "AQAB"
729
+ n: "ma2uRyBeSEOatGuDpCiV9oIxlDWix_KypDYuhQfEzqi_BiF4fV266OWfyjcABbam59aJMNvOnKW3u_eZM-PhMCBij5MZ-vcBJ4GfxDJeKSn-GP_dJ09rpDcILh8HaWAnPmMoi4DC0nrfE241wPISvZaaZnGHkOrfN_EnA5DligLgVUbrA5rJhQ1aSEQO_gf1raEOW3DZ_ACU3qhtgO0ZBG3a5h7BPiRs2sXqb2UCmBBgwyvYLDebnpE7AotF6_xBIlR-Cykdap3GHVMXhrIpvU195HF30ZoBU4dMd-AeG6HgRt4Cqy1moGoDgMQfbmQ48Hlunv9_Vi2e2CLvYECcBw"
730
+
731
+ - kid: "d01c1abe249269f72ef7ca2613a86c9f05e59567"
732
+ kty: "RSA"
733
+ alg: "RS256"
734
+ use: "sig"
735
+ e: "AQAB"
736
+ n: "0hukqytPwrj1RbMYhYoepCi3CN5k7DwYkTe_Cmb7cP9_qv4ok78KdvFXt5AnQxCRwBD7-qTNkkfMWO2RxUMBdQD0ED6tsSb1n5dp0XY8dSWiBDCX8f6Hr-KolOpvMLZKRy01HdAWcM6RoL9ikbjYHUEW1C8IJnw3MzVHkpKFDL354aptdNLaAdTCBvKzU9WpXo10g-5ctzSlWWjQuecLMQ4G1mNdsR1LHhUENEnOvgT8cDkX0fJzLbEbyBYkdMgKggyVPEB1bg6evG4fTKawgnf0IDSPxIU-wdS9wdSP9ZCJJPLi5CEp-6t6rE_sb2dGcnzjCGlembC57VwpkUvyMw"
737
+ "# ,
738
+ ) ?;
739
+ jail. create_file ( "secret" , r"c1!3n753c237" ) ?;
740
+
741
+ let config = Figment :: new ( )
742
+ . merge ( Yaml :: file ( "config.yaml" ) )
743
+ . extract_inner :: < UpstreamOAuth2Config > ( "upstream_oauth2" ) ?;
744
+
745
+ assert_eq ! ( config. providers. len( ) , 5 ) ;
746
+
747
+ assert_eq ! (
748
+ config. providers[ 1 ] . id,
749
+ Ulid :: from_str( "01GFWR32NCQ12B8Z0J8CPXRRB6" ) . unwrap( )
750
+ ) ;
751
+
752
+ assert ! ( config. providers[ 0 ] . client_secret. is_none( ) ) ;
753
+ assert ! ( matches!( config. providers[ 1 ] . client_secret, Some ( ClientSecret :: File ( ref p) ) if p == "secret" ) ) ;
754
+ assert ! ( matches!( config. providers[ 2 ] . client_secret, Some ( ClientSecret :: Value ( ref v) ) if v == "c1!3n753c237" ) ) ;
755
+ assert ! ( matches!( config. providers[ 3 ] . client_secret, Some ( ClientSecret :: File ( ref p) ) if p == "secret" ) ) ;
756
+ assert ! ( config. providers[ 4 ] . client_secret. is_none( ) ) ;
757
+
758
+ Handle :: current ( ) . block_on ( async move {
759
+ assert_eq ! ( config. providers[ 1 ] . client_secret( ) . await . unwrap( ) . unwrap( ) , "c1!3n753c237" ) ;
760
+ assert_eq ! ( config. providers[ 2 ] . client_secret( ) . await . unwrap( ) . unwrap( ) , "c1!3n753c237" ) ;
761
+ assert_eq ! ( config. providers[ 3 ] . client_secret( ) . await . unwrap( ) . unwrap( ) , "c1!3n753c237" ) ;
762
+ } ) ;
763
+
764
+ Ok ( ( ) )
765
+ } ) ;
766
+ } ) . await . unwrap ( ) ;
767
+ }
768
+ }
0 commit comments