Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/describe_global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down
77 changes: 76 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>,
) -> Result<&mut Self, Error> {
self.set_login_endpoint(login_endpoint.as_ref());
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I like that part, feel free to recommend any other design

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(&params)
.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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down