@@ -11,6 +11,7 @@ use crate::ParsedUrl;
11
11
use crate :: WpUuid ;
12
12
13
13
const KEY_APPLICATION_PASSWORDS : & str = "application-passwords" ;
14
+ const KEY_OAUTH_2 : & str = "oauth2" ;
14
15
15
16
mod login_client;
16
17
mod url_discovery;
@@ -57,27 +58,147 @@ pub struct WpApiDetails {
57
58
pub gmt_offset : i64 ,
58
59
pub timezone_string : String ,
59
60
pub namespaces : Vec < String > ,
60
- pub authentication : HashMap < String , WpRestApiAuthenticationScheme > ,
61
+ pub authentication : HashMap < String , AuthenticationProtocol > ,
61
62
pub site_icon_url : Option < String > ,
62
63
}
63
64
64
65
#[ uniffi:: export]
65
66
impl WpApiDetails {
66
67
pub fn find_application_passwords_authentication_url ( & self ) -> Option < String > {
67
- self . authentication
68
- . get ( KEY_APPLICATION_PASSWORDS )
69
- . map ( |auth_scheme| auth_scheme. endpoints . authorization . clone ( ) )
68
+ match self . authentication . get ( KEY_APPLICATION_PASSWORDS ) {
69
+ Some ( AuthenticationProtocol :: ApplicationPassword ( scheme) ) => {
70
+ Some ( scheme. endpoints . authorization . clone ( ) )
71
+ }
72
+ _ => None ,
73
+ }
74
+ }
75
+
76
+ pub fn find_oauth_server_details ( & self ) -> Option < WpApiOAuth2ServerDetails > {
77
+ match self . authentication . get ( KEY_OAUTH_2 ) {
78
+ Some ( AuthenticationProtocol :: OAuth2 ( scheme) ) => Some ( scheme. clone ( ) . into ( ) ) ,
79
+ _ => None ,
80
+ }
81
+ }
82
+
83
+ pub fn registered_authentication_methods ( & self ) -> Vec < WpAuthenticationProtocol > {
84
+ let mut methods: Vec < WpAuthenticationProtocol > = vec ! [ ] ;
85
+
86
+ for ( name, protocol) in & self . authentication {
87
+ methods. push ( protocol. clone ( ) . into ( ) )
88
+ }
89
+
90
+ methods
91
+ }
92
+ }
93
+
94
+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
95
+ #[ serde( untagged) ]
96
+ pub enum AuthenticationProtocol {
97
+ OAuth2 ( OAuth2Scheme ) ,
98
+ ApplicationPassword ( ApplicationPasswordScheme ) ,
99
+ Other ( UnknownAuthenticationData ) ,
100
+ }
101
+
102
+ #[ derive( Debug , Clone , uniffi:: Enum ) ]
103
+ pub enum WpAuthenticationProtocol {
104
+ OAuth2 ( WpApiOAuth2ServerDetails ) ,
105
+ ApplicationPassword ( String ) ,
106
+ Other ( UnknownAuthenticationData ) ,
107
+ }
108
+
109
+ impl From < AuthenticationProtocol > for WpAuthenticationProtocol {
110
+ fn from ( protocol : AuthenticationProtocol ) -> Self {
111
+ match protocol {
112
+ AuthenticationProtocol :: OAuth2 ( scheme) => {
113
+ WpAuthenticationProtocol :: OAuth2 ( scheme. clone ( ) . into ( ) )
114
+ }
115
+ AuthenticationProtocol :: ApplicationPassword ( scheme) => {
116
+ WpAuthenticationProtocol :: ApplicationPassword (
117
+ scheme. endpoints . authorization . clone ( ) ,
118
+ )
119
+ }
120
+ AuthenticationProtocol :: Other ( scheme) => {
121
+ WpAuthenticationProtocol :: Other ( scheme. clone ( ) )
122
+ }
123
+ }
70
124
}
71
125
}
72
126
73
- #[ derive( Debug , Serialize , Deserialize , uniffi:: Record ) ]
74
- pub struct WpRestApiAuthenticationScheme {
75
- pub endpoints : WpRestApiAuthenticationEndpoint ,
127
+ #[ derive( Debug , Serialize , Deserialize , Clone , uniffi:: Enum ) ]
128
+ #[ serde( untagged) ]
129
+ pub enum UnknownAuthenticationData {
130
+ Bool ( bool ) ,
131
+ Int ( i64 ) ,
132
+ String ( String ) ,
133
+ Float ( f64 ) ,
134
+ Object ( HashMap < String , UnknownAuthenticationData > ) ,
135
+ Dictionary ( HashMap < String , String > ) ,
136
+ List ( Vec < UnknownAuthenticationData > ) ,
137
+ }
138
+
139
+ /// An internal JSON representation of the WP Core `application-passwords` authentication method.
140
+ ///
141
+ #[ derive( Debug , Serialize , Deserialize , Clone , uniffi:: Record ) ]
142
+ pub struct ApplicationPasswordScheme {
143
+ endpoints : ApplicationPasswordEndpoints ,
76
144
}
77
145
78
- #[ derive( Debug , Serialize , Deserialize , uniffi:: Record ) ]
79
- pub struct WpRestApiAuthenticationEndpoint {
80
- pub authorization : String ,
146
+ /// An internal JSON representation of the WP Core `application-passwords` authentication method's endpoints.
147
+ #[ derive( Debug , Serialize , Deserialize , Clone , uniffi:: Record ) ]
148
+ pub struct ApplicationPasswordEndpoints {
149
+ authorization : String ,
150
+ }
151
+
152
+ /// An internal JSON representation of an `oauth2` authentication method as provided by https://wordpress.org/plugins/oauth2-provider/
153
+ /// Provides a fallback for servers that use an `endpoints` key to match the `application-passwords` method.
154
+ ///
155
+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
156
+ #[ serde( untagged) ]
157
+ pub enum OAuth2Scheme {
158
+ WithoutEndpointKey ( OAuth2SchemeWithoutEndpoint ) ,
159
+ WithEndpointKey ( OAuth2SchemeWithEndpoint ) ,
160
+ }
161
+
162
+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
163
+ pub struct OAuth2SchemeWithEndpoint {
164
+ endpoints : OAuth2Endpoints ,
165
+ }
166
+
167
+ /// An internal JSON representation of the `oauth2` authentication method's endpoints.
168
+ ///
169
+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
170
+ pub struct OAuth2Endpoints {
171
+ authorization : String ,
172
+ token : String ,
173
+ }
174
+
175
+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
176
+ pub struct OAuth2SchemeWithoutEndpoint {
177
+ authorize : String ,
178
+ token : String ,
179
+ }
180
+
181
+ /// A derived representation of `OAuth2Scheme` for clients that normalizes the fields
182
+ ///
183
+ #[ derive( Debug , Serialize , Deserialize , Clone , uniffi:: Record ) ]
184
+ pub struct WpApiOAuth2ServerDetails {
185
+ pub authorization_url : String ,
186
+ pub token_url : String ,
187
+ }
188
+
189
+ impl From < OAuth2Scheme > for WpApiOAuth2ServerDetails {
190
+ fn from ( scheme : OAuth2Scheme ) -> Self {
191
+ match scheme {
192
+ OAuth2Scheme :: WithoutEndpointKey ( subscheme) => WpApiOAuth2ServerDetails {
193
+ authorization_url : subscheme. authorize . clone ( ) ,
194
+ token_url : subscheme. token . clone ( ) ,
195
+ } ,
196
+ OAuth2Scheme :: WithEndpointKey ( subscheme) => WpApiOAuth2ServerDetails {
197
+ authorization_url : subscheme. endpoints . authorization . clone ( ) ,
198
+ token_url : subscheme. endpoints . token . clone ( ) ,
199
+ } ,
200
+ }
201
+ }
81
202
}
82
203
83
204
#[ derive( Debug , PartialEq , Eq , PartialOrd , Ord , Serialize , Deserialize , uniffi:: Record ) ]
@@ -200,4 +321,64 @@ mod tests {
200
321
) ;
201
322
assert_eq ! ( auth_url, ParsedUrl :: parse( expected_url. as_str( ) ) . unwrap( ) ) ;
202
323
}
324
+
325
+ #[ derive( Debug , Serialize , Deserialize ) ]
326
+ struct AuthenticationTest {
327
+ authentication : HashMap < String , AuthenticationProtocol > ,
328
+ }
329
+
330
+ #[ rstest]
331
+ #[ case( r#"{ "authentication": { } }"# ) ]
332
+ // #[case(r#"{ "authentication": [ ] }"#)] // TODO
333
+ fn test_empty_authentication_can_be_parsed ( #[ case] input_json : & str ) {
334
+ let test_object: AuthenticationTest = serde_json:: from_str ( input_json) . unwrap ( ) ;
335
+ assert ! ( test_object. authentication. is_empty( ) )
336
+ }
337
+
338
+ #[ rstest]
339
+ fn test_authentication_with_valid_application_passwords ( ) {
340
+ let input_json = r#"
341
+ { "authentication": { "application-passwords": { "endpoints": { "authorization": "http:\/\/localhost\/wp-admin\/authorize-application.php" } } } }"# ;
342
+ let test_object: AuthenticationTest = serde_json:: from_str ( input_json) . unwrap ( ) ;
343
+ assert ! ( matches!(
344
+ test_object
345
+ . authentication
346
+ . get( KEY_APPLICATION_PASSWORDS )
347
+ . unwrap( ) ,
348
+ AuthenticationProtocol :: ApplicationPassword ( _)
349
+ ) ) ;
350
+ }
351
+
352
+ #[ rstest]
353
+ #[ case( r#"{ "authentication": { "application-passwords": { } } }"# ) ]
354
+ #[ case( r#"{ "authentication": { "application-passwords": [ ] } }"# ) ]
355
+ #[ case( r#"{ "authentication": { "application-passwords": { "disabled": true } } }"# ) ]
356
+ #[ case( r#"{ "authentication": { "application-passwords": { "florps": 42 } } }"# ) ]
357
+ #[ case( r#"{ "authentication": { "application-passwords": { "florps": -42 } } }"# ) ]
358
+ #[ case( r#"{ "authentication": { "application-passwords": { "florps": 0.5234 } } }"# ) ]
359
+ fn test_authentication_with_invalid_application_passwords_is_other ( #[ case] input_json : & str ) {
360
+ let test_object: AuthenticationTest = serde_json:: from_str ( input_json) . unwrap ( ) ;
361
+ assert ! ( matches!(
362
+ test_object
363
+ . authentication
364
+ . get( KEY_APPLICATION_PASSWORDS )
365
+ . unwrap( ) ,
366
+ AuthenticationProtocol :: Other ( _)
367
+ ) )
368
+ }
369
+
370
+ #[ rstest]
371
+ #[ case( r#"{ "authentication": { "oauth2": { "authorize": "http:\/\/localhost\/oauth\/authorize", "token": "http:\/\/localhost\/oauth\/token", "me": "http:\/\/localhost\/oauth\/me", "version": "2.0", "software": "WP OAuth Server" } } }"# ) ]
372
+ #[ case( r#"{ "authentication": { "oauth2": { "endpoints": { "authorization": "https:\/\/public-api.wordpress.com\/oauth2\/authorize", "token": "https:\/\/public-api.wordpress.com\/oauth2\/token" } } } }"# ) ]
373
+ fn test_authentication_with_valid_oauth2 ( #[ case] input_json : & str ) {
374
+ let test_object: AuthenticationTest = serde_json:: from_str ( input_json) . unwrap ( ) ;
375
+ println ! ( "{:?}" , test_object) ;
376
+ assert ! ( matches!(
377
+ test_object
378
+ . authentication
379
+ . get( KEY_OAUTH_2 )
380
+ . unwrap( ) ,
381
+ AuthenticationProtocol :: OAuth2 ( _)
382
+ ) ) ;
383
+ }
203
384
}
0 commit comments