From b31b10e985cfee6022d87b961159d65e938df436 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Tue, 25 Feb 2025 10:29:23 +0100 Subject: [PATCH 1/2] fix describe_global example --- examples/describe_global.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/describe_global.rs b/examples/describe_global.rs index 63582e4..8d5ec4f 100644 --- a/examples/describe_global.rs +++ b/examples/describe_global.rs @@ -8,7 +8,7 @@ async fn main() -> Result<(), Error> { let username = env::var("SFDC_USERNAME").unwrap(); let password = env::var("SFDC_PASSWORD").unwrap(); - let mut client = Client::new(client_id, client_secret); + let mut client = Client::new(Some(client_id), Some(client_secret)); client.login_with_credential(username, password).await?; let res = client.describe_global().await?; From 1c607362e84c668c80182076ce8c58523c36e6ce Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Tue, 25 Feb 2025 10:29:31 +0100 Subject: [PATCH 2/2] support client credentials authentication --- src/client.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index 3acea33..43dfc6d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -100,6 +100,46 @@ impl Client { } } + /// Login to Salesforce using client credentials authenticication + /// + /// Client credentials auth for server-to-server authentication. + /// https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_client_credentials_flow.htm&type=5 + /// + /// In contrast to the other login methods, we need the company specific login endpoint + /// (e.g. `https://MyDomainName.my.salesforce.com`) instead of the generic login endpoint. + pub async fn login_with_client_credentials( + &mut self, + login_endpoint: impl AsRef, + ) -> Result<&mut Self, Error> { + self.set_login_endpoint(login_endpoint.as_ref()); + let token_url = format!("{}/services/oauth2/token", login_endpoint.as_ref()); + let params = [ + ("grant_type", "client_credentials"), + ("client_id", self.client_id.as_ref().unwrap()), + ("client_secret", self.client_secret.as_ref().unwrap()), + ]; + let res = self + .http_client + .post(token_url.as_str()) + .form(¶ms) + .send() + .await?; + + if res.status().is_success() { + let r: TokenResponse = res.json().await?; + self.access_token = Some(AccessToken { + value: r.access_token, + issued_at: r.issued_at, + token_type: r.token_type.ok_or(Error::NotLoggedIn)?, + }); + self.instance_url = Some(r.instance_url); + Ok(self) + } else { + let error_response = res.json().await?; + Err(Error::TokenError(error_response)) + } + } + /// Login to Salesforce with username and password pub async fn login_with_credential( &mut self, @@ -136,7 +176,11 @@ impl Client { } } - pub async fn login_by_soap(&mut self, username: String, password: String) -> Result<&mut Self, Error> { + pub async fn login_by_soap( + &mut self, + username: String, + password: String, + ) -> Result<&mut Self, Error> { let token_url = format!( "{login_endpoint}/services/Soap/u/{version}", login_endpoint = self.login_endpoint, @@ -558,6 +602,37 @@ mod tests { name: String, } + #[tokio::test] + async fn login_with_client_credentials() -> Result<(), Error> { + let _m = mock("POST", "/services/oauth2/token") + .match_body("grant_type=client_credentials&client_id=aaa&client_secret=bbb") + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + json!({ + "access_token": "this_is_access_token", + "issued_at": "2019-10-01 00:00:00", + "id": "12345", + "instance_url": "https://ap.salesforce.com", + "signature": "abcde", + "token_type": "Bearer", + }) + .to_string(), + ) + .create(); + + let mut client = super::Client::new(Some("aaa".to_string()), Some("bbb".to_string())); + let url = &mockito::server_url(); + client.login_with_client_credentials(&url).await?; + let token = client.access_token.unwrap(); + assert_eq!("this_is_access_token", token.value); + assert_eq!("Bearer", token.token_type); + assert_eq!("2019-10-01 00:00:00", token.issued_at); + assert_eq!("https://ap.salesforce.com", client.instance_url.unwrap()); + + Ok(()) + } + #[tokio::test] async fn login_with_credentials() -> Result<(), Error> { let _m = mock("POST", "/services/oauth2/token")