From cd3e2e3ed59679f460947d14e8c0d16cafa674fc Mon Sep 17 00:00:00 2001 From: Bill Fraser Date: Mon, 6 May 2024 11:20:25 -0700 Subject: [PATCH] fix for oauth2 refresh tokens obtained using client secret (#152) They are different from tokens obtained with the PKCE flow, and need the client secret to be provided again along with the refresh token when obtaining new tokens. --- Cargo.toml | 2 +- RELEASE_NOTES.md | 7 +++++++ src/oauth2.rs | 46 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 942bb4c..0d3236f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dropbox-sdk" -version = "0.18.0" +version = "0.18.1" authors = ["Bill Fraser "] edition = "2018" description = "Rust bindings to the Dropbox API, generated by Stone from the official spec." diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9795da0..4cd75fb 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,10 @@ +# v0.18.1 +2024-05-06 +* fixed bug when using oauth2 refresh tokens using client secret instead of PKCE: + * See https://github.com/dropbox/dropbox-sdk-rust/issues/151 for details + * New function Authorize::from_client_secret_refresh_token() should be used for any refresh tokens previously obtained using this flow + * Thanks to Peerat Vichivanives for reporting and testing the fix + # v0.18.0 2024-01-12 * MSRV raised to 1.65.0 diff --git a/src/oauth2.rs b/src/oauth2.rs index ce08883..179470f 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -293,6 +293,7 @@ enum AuthorizationState { }, Refresh { refresh_token: String, + client_secret: Option, }, AccessToken { client_secret: Option, @@ -365,14 +366,34 @@ impl Authorization { }) } - /// Recreate the authorization from a refresh token. + /// Recreate the authorization from a refresh token obtained using the [`Oauth2Type::PKCE`] + /// flow. pub fn from_refresh_token( client_id: String, refresh_token: String, ) -> Self { Self { client_id, - state: AuthorizationState::Refresh { refresh_token }, + state: AuthorizationState::Refresh { + refresh_token, + client_secret: None, + }, + } + } + + /// Recreate the authorization from a refresh token obtained using the + /// [`Oauth2Type::AuthorizationCode`] flow. This requires the client secret as well. + pub fn from_client_secret_refresh_token( + client_id: String, + client_secret: String, + refresh_token: String, + ) -> Self { + Self { + client_id, + state: AuthorizationState::Refresh { + refresh_token, + client_secret: Some(client_secret), + }, } } @@ -431,8 +452,11 @@ impl Authorization { auth_code = Some(code); redirect_uri = uri; } - AuthorizationState::Refresh { refresh_token: refresh } => { + AuthorizationState::Refresh { refresh_token: refresh, client_secret: secret } => { refresh_token = Some(refresh); + if let Some(secret) = secret { + client_secret = Some(secret); + } } } @@ -448,14 +472,12 @@ impl Authorization { params.append_pair("client_id", &self.client_id); - if refresh_token.is_none() { - if let Some(pkce) = pkce_code { - params.append_pair("code_verifier", &pkce); - } else { - params.append_pair( - "client_secret", - client_secret.as_ref().expect("need either PKCE code or client secret")); - } + if let Some(client_secret) = client_secret.as_deref() { + params.append_pair("client_secret", client_secret); + } + + if let Some(pkce) = pkce_code { + params.append_pair("code_verifier", &pkce); } if let Some(value) = redirect_uri { @@ -499,7 +521,7 @@ impl Authorization { match refresh_token { Some(refresh) => { - self.state = AuthorizationState::Refresh { refresh_token: refresh }; + self.state = AuthorizationState::Refresh { refresh_token: refresh, client_secret }; } None if !matches!(self.state, AuthorizationState::Refresh {..}) => { self.state = AuthorizationState::AccessToken {