12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- use std:: ops:: { Deref , DerefMut } ;
15
+ use std:: ops:: Deref ;
16
16
17
17
use async_trait:: async_trait;
18
+ use figment:: Figment ;
18
19
use mas_iana:: oauth:: OAuthClientAuthenticationMethod ;
19
20
use mas_jose:: jwk:: PublicJsonWebKeySet ;
20
21
use rand:: Rng ;
21
22
use schemars:: JsonSchema ;
22
- use serde:: { Deserialize , Serialize } ;
23
- use serde_with:: skip_serializing_none;
24
- use thiserror:: Error ;
23
+ use serde:: { de:: Error , Deserialize , Serialize } ;
25
24
use ulid:: Ulid ;
26
25
use url:: Url ;
27
26
@@ -41,40 +40,42 @@ impl From<PublicJsonWebKeySet> for JwksOrJwksUri {
41
40
}
42
41
43
42
/// Authentication method used by clients
44
- #[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
45
- #[ serde( tag = "client_auth_method" , rename_all = "snake_case" ) ]
43
+ #[ derive( JsonSchema , Serialize , Deserialize , Copy , Clone , Debug ) ]
44
+ #[ serde( rename_all = "snake_case" ) ]
46
45
pub enum ClientAuthMethodConfig {
47
46
/// `none`: No authentication
48
47
None ,
49
48
50
49
/// `client_secret_basic`: `client_id` and `client_secret` used as basic
51
50
/// authorization credentials
52
- ClientSecretBasic {
53
- /// The client secret
54
- client_secret : String ,
55
- } ,
51
+ ClientSecretBasic ,
56
52
57
53
/// `client_secret_post`: `client_id` and `client_secret` sent in the
58
54
/// request body
59
- ClientSecretPost {
60
- /// The client secret
61
- client_secret : String ,
62
- } ,
55
+ ClientSecretPost ,
63
56
64
57
/// `client_secret_basic`: a `client_assertion` sent in the request body and
65
58
/// signed using the `client_secret`
66
- ClientSecretJwt {
67
- /// The client secret
68
- client_secret : String ,
69
- } ,
59
+ ClientSecretJwt ,
70
60
71
61
/// `client_secret_basic`: a `client_assertion` sent in the request body and
72
62
/// signed by an asymmetric key
73
- PrivateKeyJwt ( JwksOrJwksUri ) ,
63
+ PrivateKeyJwt ,
64
+ }
65
+
66
+ impl std:: fmt:: Display for ClientAuthMethodConfig {
67
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
68
+ match self {
69
+ ClientAuthMethodConfig :: None => write ! ( f, "none" ) ,
70
+ ClientAuthMethodConfig :: ClientSecretBasic => write ! ( f, "client_secret_basic" ) ,
71
+ ClientAuthMethodConfig :: ClientSecretPost => write ! ( f, "client_secret_post" ) ,
72
+ ClientAuthMethodConfig :: ClientSecretJwt => write ! ( f, "client_secret_jwt" ) ,
73
+ ClientAuthMethodConfig :: PrivateKeyJwt => write ! ( f, "private_key_jwt" ) ,
74
+ }
75
+ }
74
76
}
75
77
76
78
/// An OAuth 2.0 client configuration
77
- #[ skip_serializing_none]
78
79
#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
79
80
pub struct ClientConfig {
80
81
/// The client ID
@@ -86,75 +87,129 @@ pub struct ClientConfig {
86
87
pub client_id : Ulid ,
87
88
88
89
/// Authentication method used for this client
89
- #[ serde( flatten) ]
90
- pub client_auth_method : ClientAuthMethodConfig ,
90
+ client_auth_method : ClientAuthMethodConfig ,
91
+
92
+ /// The client secret, used by the `client_secret_basic`,
93
+ /// `client_secret_post` and `client_secret_jwt` authentication methods
94
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
95
+ pub client_secret : Option < String > ,
96
+
97
+ /// The JSON Web Key Set (JWKS) used by the `private_key_jwt` authentication
98
+ /// method. Mutually exclusive with `jwks_uri`
99
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
100
+ pub jwks : Option < PublicJsonWebKeySet > ,
101
+
102
+ /// The URL of the JSON Web Key Set (JWKS) used by the `private_key_jwt`
103
+ /// authentication method. Mutually exclusive with `jwks`
104
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
105
+ pub jwks_uri : Option < Url > ,
91
106
92
107
/// List of allowed redirect URIs
93
- #[ serde( default ) ]
108
+ #[ serde( default , skip_serializing_if = "Vec::is_empty" ) ]
94
109
pub redirect_uris : Vec < Url > ,
95
110
}
96
111
97
- #[ derive( Debug , Error ) ]
98
- #[ error( "Invalid redirect URI" ) ]
99
- pub struct InvalidRedirectUriError ;
100
-
101
112
impl ClientConfig {
102
- #[ doc( hidden) ]
103
- #[ must_use]
104
- pub fn client_secret ( & self ) -> Option < & str > {
105
- match & self . client_auth_method {
106
- ClientAuthMethodConfig :: ClientSecretPost { client_secret }
107
- | ClientAuthMethodConfig :: ClientSecretBasic { client_secret }
108
- | ClientAuthMethodConfig :: ClientSecretJwt { client_secret } => Some ( client_secret) ,
109
- _ => None ,
113
+ fn validate ( & self ) -> Result < ( ) , figment:: error:: Error > {
114
+ let auth_method = self . client_auth_method ;
115
+ match self . client_auth_method {
116
+ ClientAuthMethodConfig :: PrivateKeyJwt => {
117
+ if self . jwks . is_none ( ) && self . jwks_uri . is_none ( ) {
118
+ let error = figment:: error:: Error :: custom (
119
+ "jwks or jwks_uri is required for private_key_jwt" ,
120
+ ) ;
121
+ return Err ( error. with_path ( "client_auth_method" ) ) ;
122
+ }
123
+
124
+ if self . jwks . is_some ( ) && self . jwks_uri . is_some ( ) {
125
+ let error =
126
+ figment:: error:: Error :: custom ( "jwks and jwks_uri are mutually exclusive" ) ;
127
+ return Err ( error. with_path ( "jwks" ) ) ;
128
+ }
129
+
130
+ if self . client_secret . is_some ( ) {
131
+ let error = figment:: error:: Error :: custom (
132
+ "client_secret is not allowed with private_key_jwt" ,
133
+ ) ;
134
+ return Err ( error. with_path ( "client_secret" ) ) ;
135
+ }
136
+ }
137
+
138
+ ClientAuthMethodConfig :: ClientSecretPost
139
+ | ClientAuthMethodConfig :: ClientSecretBasic
140
+ | ClientAuthMethodConfig :: ClientSecretJwt => {
141
+ if self . client_secret . is_none ( ) {
142
+ let error = figment:: error:: Error :: custom ( format ! (
143
+ "client_secret is required for {auth_method}"
144
+ ) ) ;
145
+ return Err ( error. with_path ( "client_auth_method" ) ) ;
146
+ }
147
+
148
+ if self . jwks . is_some ( ) {
149
+ let error = figment:: error:: Error :: custom ( format ! (
150
+ "jwks is not allowed with {auth_method}"
151
+ ) ) ;
152
+ return Err ( error. with_path ( "jwks" ) ) ;
153
+ }
154
+
155
+ if self . jwks_uri . is_some ( ) {
156
+ let error = figment:: error:: Error :: custom ( format ! (
157
+ "jwks_uri is not allowed with {auth_method}"
158
+ ) ) ;
159
+ return Err ( error. with_path ( "jwks_uri" ) ) ;
160
+ }
161
+ }
162
+
163
+ ClientAuthMethodConfig :: None => {
164
+ if self . client_secret . is_some ( ) {
165
+ let error = figment:: error:: Error :: custom (
166
+ "client_secret is not allowed with none authentication method" ,
167
+ ) ;
168
+ return Err ( error. with_path ( "client_secret" ) ) ;
169
+ }
170
+
171
+ if self . jwks . is_some ( ) {
172
+ let error = figment:: error:: Error :: custom (
173
+ "jwks is not allowed with none authentication method" ,
174
+ ) ;
175
+ return Err ( error) ;
176
+ }
177
+
178
+ if self . jwks_uri . is_some ( ) {
179
+ let error = figment:: error:: Error :: custom (
180
+ "jwks_uri is not allowed with none authentication method" ,
181
+ ) ;
182
+ return Err ( error) ;
183
+ }
184
+ }
110
185
}
186
+
187
+ Ok ( ( ) )
111
188
}
112
189
113
- # [ doc ( hidden ) ]
190
+ /// Authentication method used for this client
114
191
#[ must_use]
115
192
pub fn client_auth_method ( & self ) -> OAuthClientAuthenticationMethod {
116
- match & self . client_auth_method {
193
+ match self . client_auth_method {
117
194
ClientAuthMethodConfig :: None => OAuthClientAuthenticationMethod :: None ,
118
- ClientAuthMethodConfig :: ClientSecretBasic { .. } => {
195
+ ClientAuthMethodConfig :: ClientSecretBasic => {
119
196
OAuthClientAuthenticationMethod :: ClientSecretBasic
120
197
}
121
- ClientAuthMethodConfig :: ClientSecretPost { .. } => {
198
+ ClientAuthMethodConfig :: ClientSecretPost => {
122
199
OAuthClientAuthenticationMethod :: ClientSecretPost
123
200
}
124
- ClientAuthMethodConfig :: ClientSecretJwt { .. } => {
201
+ ClientAuthMethodConfig :: ClientSecretJwt => {
125
202
OAuthClientAuthenticationMethod :: ClientSecretJwt
126
203
}
127
- ClientAuthMethodConfig :: PrivateKeyJwt ( _) => {
128
- OAuthClientAuthenticationMethod :: PrivateKeyJwt
129
- }
130
- }
131
- }
132
-
133
- #[ doc( hidden) ]
134
- #[ must_use]
135
- pub fn jwks ( & self ) -> Option < & PublicJsonWebKeySet > {
136
- match & self . client_auth_method {
137
- ClientAuthMethodConfig :: PrivateKeyJwt ( JwksOrJwksUri :: Jwks ( jwks) ) => Some ( jwks) ,
138
- _ => None ,
139
- }
140
- }
141
-
142
- #[ doc( hidden) ]
143
- #[ must_use]
144
- pub fn jwks_uri ( & self ) -> Option < & Url > {
145
- match & self . client_auth_method {
146
- ClientAuthMethodConfig :: PrivateKeyJwt ( JwksOrJwksUri :: JwksUri ( jwks_uri) ) => {
147
- Some ( jwks_uri)
148
- }
149
- _ => None ,
204
+ ClientAuthMethodConfig :: PrivateKeyJwt => OAuthClientAuthenticationMethod :: PrivateKeyJwt ,
150
205
}
151
206
}
152
207
}
153
208
154
209
/// List of OAuth 2.0/OIDC clients config
155
210
#[ derive( Debug , Clone , Default , Serialize , Deserialize , JsonSchema ) ]
156
211
#[ serde( transparent) ]
157
- pub struct ClientsConfig ( Vec < ClientConfig > ) ;
212
+ pub struct ClientsConfig ( # [ schemars ( with = "Vec::<ClientConfig>" ) ] Vec < ClientConfig > ) ;
158
213
159
214
impl Deref for ClientsConfig {
160
215
type Target = Vec < ClientConfig > ;
@@ -164,12 +219,6 @@ impl Deref for ClientsConfig {
164
219
}
165
220
}
166
221
167
- impl DerefMut for ClientsConfig {
168
- fn deref_mut ( & mut self ) -> & mut Self :: Target {
169
- & mut self . 0
170
- }
171
- }
172
-
173
222
impl IntoIterator for ClientsConfig {
174
223
type Item = ClientConfig ;
175
224
type IntoIter = std:: vec:: IntoIter < ClientConfig > ;
@@ -190,6 +239,21 @@ impl ConfigurationSection for ClientsConfig {
190
239
Ok ( Self :: default ( ) )
191
240
}
192
241
242
+ fn validate ( & self , figment : & Figment ) -> Result < ( ) , figment:: error:: Error > {
243
+ for ( index, client) in self . 0 . iter ( ) . enumerate ( ) {
244
+ client. validate ( ) . map_err ( |mut err| {
245
+ // Save the error location information in the error
246
+ err. metadata = figment. find_metadata ( Self :: PATH . unwrap ( ) ) . cloned ( ) ;
247
+ err. profile = Some ( figment:: Profile :: Default ) ;
248
+ err. path . insert ( 0 , Self :: PATH . unwrap ( ) . to_owned ( ) ) ;
249
+ err. path . insert ( 1 , format ! ( "{index}" ) ) ;
250
+ err
251
+ } ) ?;
252
+ }
253
+
254
+ Ok ( ( ) )
255
+ }
256
+
193
257
fn test ( ) -> Self {
194
258
Self :: default ( )
195
259
}
0 commit comments