Skip to content

Commit 206eab1

Browse files
authored
Add support for the /oauth2/token-info endpoint (#733)
* Add support for the /oauth2/token-info endpoint * Wrap `UniffiWpComApiClient` instead of aliasing it * Clippy fixes
1 parent d19d764 commit 206eab1

File tree

11 files changed

+174
-5
lines changed

11 files changed

+174
-5
lines changed

native/swift/Sources/wordpress-api/Exports.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public typealias MiddlewarePipeline = WordPressAPIInternal.WpApiMiddlewarePipeli
1919
public typealias MiddlewarePipelineBuilder = WordPressAPIInternal.WpApiMiddlewarePipelineBuilder
2020
public typealias ApiDiscoveryAuthenticationMiddleware = WordPressAPIInternal.ApiDiscoveryAuthenticationMiddleware
2121
public typealias RetryAfterMiddleware = WordPressAPIInternal.RetryAfterMiddleware
22+
public typealias WpApiClientDelegate = WordPressAPIInternal.WpApiClientDelegate
23+
public typealias WpAppNotifier = WordPressAPIInternal.WpAppNotifier
2224

2325
// MARK: - Login
2426

@@ -129,3 +131,6 @@ public typealias WpSiteHealthTestsRequestExecutor = WordPressAPIInternal.WpSiteH
129131

130132
// MARK: - WordPress.org
131133
public typealias WordPressOrgApiClient = WordPressAPIInternal.WordPressOrgApiClient
134+
135+
// MARK: - WordPress.com
136+
public typealias TokenValidationParameters = WordPressAPIInternal.TokenValidationParameters
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Foundation
2+
import WordPressAPIInternal
3+
4+
public class WPComApiClient {
5+
private let internalClient: WordPressAPIInternal.UniffiWpComApiClient
6+
private let delegate: WpApiClientDelegate
7+
8+
public init(delegate: WpApiClientDelegate) {
9+
self.delegate = delegate // We need to retain this ourselves because it's passed to a Rust object
10+
self.internalClient = UniffiWpComApiClient(delegate: delegate)
11+
}
12+
13+
public var oauth2: Oauth2RequestExecutor {
14+
internalClient.oauth2()
15+
}
16+
17+
public var jetpackConnection: JetpackConnectionRequestExecutor {
18+
internalClient.jetpackConnection()
19+
}
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
import WordPressAPI
3+
4+
final class MockAppNotifier: WpAppNotifier {
5+
func requestedWithInvalidAuthentication() async {
6+
// no-op
7+
}
8+
}

wp_api/src/wp_com/client.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use super::endpoint::jetpack_connection_endpoint::{
22
JetpackConnectionRequestBuilder, JetpackConnectionRequestExecutor,
33
};
4+
use super::endpoint::oauth2::{Oauth2RequestBuilder, Oauth2RequestExecutor};
5+
use crate::api_client_generate_request_builder;
46
use crate::{
57
ParsedUrl, WpApiClientDelegate, api_client_generate_api_client,
68
api_client_generate_endpoint_impl, auth::WpAuthenticationProvider,
@@ -24,14 +26,17 @@ impl UniffiWpComApiRequestBuilder {
2426

2527
pub struct WpComApiRequestBuilder {
2628
jetpack_connection: Arc<JetpackConnectionRequestBuilder>,
29+
oauth2: Arc<Oauth2RequestBuilder>,
2730
}
2831

2932
impl WpComApiRequestBuilder {
3033
pub fn new(api_root_url: Arc<ParsedUrl>, auth_provider: Arc<WpAuthenticationProvider>) -> Self {
31-
Self {
32-
jetpack_connection: JetpackConnectionRequestBuilder::new(api_root_url, auth_provider)
33-
.into(),
34-
}
34+
api_client_generate_request_builder!(
35+
api_root_url,
36+
auth_provider;
37+
jetpack_connection,
38+
oauth2
39+
)
3540
}
3641
}
3742

@@ -52,6 +57,7 @@ impl UniffiWpComApiClient {
5257

5358
pub struct WpComApiClient {
5459
jetpack_connection: Arc<JetpackConnectionRequestExecutor>,
60+
oauth2: Arc<Oauth2RequestExecutor>,
5561
}
5662

5763
impl WpComApiClient {
@@ -62,8 +68,10 @@ impl WpComApiClient {
6268
api_client_generate_api_client!(
6369
api_root_url,
6470
delegate;
65-
jetpack_connection
71+
jetpack_connection,
72+
oauth2
6673
)
6774
}
6875
}
6976
api_client_generate_endpoint_impl!(WpComApi, jetpack_connection);
77+
api_client_generate_endpoint_impl!(WpComApi, oauth2);

wp_api/src/wp_com/endpoint.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod jetpack_connection_endpoint;
2+
pub mod oauth2;

wp_api/src/wp_com/endpoint/oauth2.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use crate::wp_com::oauth2::{TokenValidationParameters, TokenValidationResponse};
2+
use crate::{
3+
request::endpoint::{AsNamespace, DerivedRequest},
4+
wp_com::WpComNamespace,
5+
};
6+
use wp_derive_request_builder::WpDerivedRequest;
7+
8+
#[derive(WpDerivedRequest)]
9+
enum Oauth2Request {
10+
#[get(url = "/token-info", params = &TokenValidationParameters, output = TokenValidationResponse)]
11+
FetchInfo,
12+
}
13+
14+
impl DerivedRequest for Oauth2Request {
15+
fn namespace() -> impl AsNamespace {
16+
WpComNamespace::Oauth2
17+
}
18+
}

wp_api/src/wp_com/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::{num::ParseIntError, str::FromStr};
55
pub mod client;
66
pub mod endpoint;
77
pub mod jetpack_connection;
8+
pub mod oauth2;
89

910
impl_as_query_value_for_new_type!(WpComSiteId);
1011
uniffi::custom_newtype!(WpComSiteId, u64);
@@ -26,12 +27,14 @@ impl std::fmt::Display for WpComSiteId {
2627
}
2728

2829
pub(crate) enum WpComNamespace {
30+
Oauth2,
2931
V2,
3032
}
3133

3234
impl AsNamespace for WpComNamespace {
3335
fn as_str(&self) -> &str {
3436
match self {
37+
WpComNamespace::Oauth2 => "/oauth2",
3538
WpComNamespace::V2 => "/wpcom/v2",
3639
}
3740
}

wp_api/src/wp_com/oauth2.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use crate::url_query::AppendUrlQueryPairs;
2+
use crate::url_query::QueryPairs;
3+
use serde::{Deserialize, Serialize};
4+
use wp_serde_helper::deserialize_u64_or_string;
5+
6+
#[derive(Debug, Serialize, uniffi::Record)]
7+
pub struct TokenValidationParameters {
8+
pub client_id: String,
9+
pub token: String,
10+
}
11+
12+
impl AppendUrlQueryPairs for TokenValidationParameters {
13+
fn append_query_pairs(&self, query_pairs_mut: &mut QueryPairs) {
14+
query_pairs_mut.append_pair("client_id", &self.client_id);
15+
query_pairs_mut.append_pair("token", &self.token);
16+
}
17+
}
18+
19+
#[derive(Debug, Serialize, Deserialize, uniffi::Record)]
20+
pub struct TokenValidationResponse {
21+
#[serde(default, deserialize_with = "deserialize_u64_or_string")]
22+
pub client_id: u64,
23+
#[serde(default, deserialize_with = "deserialize_u64_or_string")]
24+
pub user_id: u64,
25+
pub blog_id: Option<u64>,
26+
pub scope: String,
27+
}
28+
29+
#[cfg(test)]
30+
mod tests {
31+
use super::*;
32+
use url::Url;
33+
34+
#[test]
35+
fn test_token_validation_parameters_append_query_pairs() {
36+
let mut url = Url::parse("https://public-api.wordpress.com/oauth2/token-info")
37+
.expect("Failed to parse url");
38+
39+
let params = TokenValidationParameters {
40+
client_id: "11".to_string(),
41+
token: "test_token".to_string(),
42+
};
43+
44+
let mut query_pairs = url.query_pairs_mut();
45+
params.append_query_pairs(&mut query_pairs);
46+
47+
assert_eq!(
48+
query_pairs.finish().as_str(),
49+
"https://public-api.wordpress.com/oauth2/token-info?client_id=11&token=test_token"
50+
);
51+
}
52+
53+
#[test]
54+
fn test_token_validation_response_deserialization() {
55+
let json_str =
56+
r#"{"client_id":"11","user_id":"1234567890","blog_id":null,"scope":"global"}"#;
57+
let response: TokenValidationResponse = serde_json::from_str(json_str).unwrap();
58+
59+
assert_eq!(response.client_id, 11);
60+
assert_eq!(response.user_id, 1234567890);
61+
assert_eq!(response.blog_id, None);
62+
assert_eq!(response.scope, "global");
63+
}
64+
65+
#[test]
66+
fn test_token_validation_error_response() {
67+
let json_str =
68+
r#"{"error":"invalid_request","error_description":"The specified token is invalid."}"#;
69+
let result = serde_json::from_str::<TokenValidationResponse>(json_str);
70+
71+
assert!(result.is_err());
72+
}
73+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"error":"invalid_request","error_description":"The specified token is invalid."}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"client_id":"11","user_id":"1234567890","blog_id":null,"scope":"global"}

0 commit comments

Comments
 (0)