20 个版本 (9 个稳定版)
新 2.0.1 | 2024 年 8 月 11 日 |
---|---|
2.0.0 | 2024 年 5 月 23 日 |
2.0.0-beta.0 | 2024 年 1 月 6 日 |
1.1.4 | 2024 年 2 月 1 日 |
0.0.1 | 2020 年 12 月 13 日 |
#1 in #microsoft
1,314 每月下载量
2MB
43K SLoC
graph-rs-sdk
Microsoft Graph 和 Microsoft Identity Platform 的 Rust SDK 客户端
在 crates.io 上可用 - v2.0.1 - 最新稳定版本
功能概述
Microsoft Graph V1 和 Beta API 客户端
- 广泛支持 Graph API
- 使用 Streaming、Channels 或 Iterators 进行分页
- 上传会话、OData 查询和文件下载
Microsoft Identity Platform(获取访问令牌)
- 授权码、客户端凭证、设备码、OpenId
- 内存令牌缓存
- 自动刷新令牌
- 交互式 WebView 身份验证(功能 =
interactive-auth
) - X509 证书(功能 =
openssl
)和证明密钥代码交换(PKCE)支持
还有更多。有关功能列表的更全面信息,请参阅 功能。有关可以在 crate 中启用的功能的详细信息,请参阅 Cargo 功能标志。
graph-rs-sdk = "2.0.1"
tokio = { version = "1.25.0", features = ["full"] }
用于将实现 serde Serialize
的类型用作请求体或将 serde 的 json 宏传递
serde = { version = "1", features = ["derive"] }
serde_json = "1"
要使用 stream 功能,请添加 futures crate
futures = "0.3"
并导入 futures::StreamExt
。
use futures::StreamExt;
use graph_rs_sdk::*;
功能
图客户端
OAuth 和 Openid
- OAuth - 获取访问令牌
- 身份平台支持
- 凭证
- 自动刷新令牌
- 当前仅提供内存中的令牌缓存用于令牌持久化。正在积极开发其他持久化机制,例如Azure Key Vault和桌面机制(如MacOS KeyChain),这些将在2.0.0版本之后发布。您可以在https://github.com/sreeise/graph-rs-sdk/issues/432跟踪此进度。
- 交互式身份验证(WebView)
- 授权码授予
- OpenId
- 客户端凭证
- 使用“登录”获取授权码的流程的URL构建器 - 构建“登录”URL
- 交互式身份验证示例(功能 =
interactive-auth
) - 证书身份验证(功能 =
openssl
)
可用的API有哪些
可用的API是从存储在Microsoft的msgraph-metadata存储库中的OpenApi配置生成的,用于Graph API。可能有一些请求和/或API尚未包含在此项目中,但通常大多数已实现。
Cargo 功能标志
interactive-auth
:在支持WebView的平台(如桌面)上使用WebView进行交互式身份验证。使用wry和tao crate进行WebView支持。支持Linux和Windows平台。目前,不支持MacOS - 此工作的正在进行中。openssl
:启用在客户端凭据和授权码身份验证流程中使用证书的功能。此外,还启用相关类型,如X509Certificate,用于构建/运行基于证书的身份验证流程。test-util
:启用仅测试功能。目前,这仅启用在http客户端中关闭https的功能,以便使用crate的mocking框架。未来可能会添加其他与测试相关的功能。native-tls
:在reqwest http-client中启用功能native-tls。有关更多详细信息,请参阅reqwest crate。rustls-tls
:在reqwest http-client中启用功能rustls-tls。有关更多详细信息,请参阅reqwest crate。brotli
:在reqwest http-client中启用功能brotli。有关更多详细信息,请参阅reqwest crate。deflate
:在reqwest http-client中启用功能deflate。有关更多详细信息,请参阅reqwest crate。trust-dns
:在reqwest http-client中启用功能trust-dns。有关更多详细信息,请参阅reqwest crate。socks
:在reqwest http-client中启用功能socks(socks代理支持)。有关更多详细信息,请参阅reqwest crate。
用法
有关大量示例,请参阅GitHub上的examples目录
异步和阻塞客户端
该crate可以同时进行异步和阻塞请求。
异步客户端(默认)
graph-rs-sdk = "2.0.1"
tokio = { version = "1.25.0", features = ["full"] }
示例
use graph_rs_sdk::*;
#[tokio::main]
async fn main() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.users()
.list_user()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
阻塞客户端
要使用阻塞客户端,请使用into_blocking()
方法。使用阻塞客户端时,不应使用tokio
。
graph-rs-sdk = "2.0.1"
示例
use graph_rs_sdk::*;
fn main() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.users()
.list_user()
.into_blocking()
.send()?;
println!("{:#?}", response);
let body: serde_json::Value = response.json()?;
println!("{:#?}", body);
Ok(())
}
发送方法
send()方法是将请求发送到主方法,并返回一个Result<rewest::Response, GraphFailure>
。有关Response类型的详细信息,请参阅reqwest crate。
use graph_rs_sdk::*;
pub async fn get_drive_item() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.me()
.drive()
.get_drive()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
响应错误/错误类型
虽然该库有自己的错误类型和结果,但由于可能出现的错误数量很多,你可能会想使用诸如anyhow之类的库。
如果Microsoft Graph API返回了错误,这几乎总是出现在响应体中。异步请求的ResponseExt
和阻塞请求的BlockingResponseExt
提供了方便的方法,可以用来从响应体中获取错误信息。
use graph_rs_sdk::{http::ResponseExt, Graph};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = GraphClient::new("token");
let response = client.users().list_user().send().await?;
if !response.status().is_success() {
if let Ok(error) = response.into_graph_error_message().await {
println!("{error:#?}");
}
}
Ok(())
}
自定义类型
您可以通过实现serde::Serialize
将您自己的类型传递给需要请求体的API请求。
您可以通过利用reqwest::Response的方法来实现自己的类型。这些类型必须实现serde::Deserialize
。有关更多信息,请参阅reqwest库。
#[macro_use]
extern crate serde;
use graph_rs_sdk::*;
#[derive(Debug, Serialize, Deserialize)]
pub struct DriveItem {
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
// ... Any other fields
}
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
static ITEM_ID: &str = "ITEM_ID";
pub async fn get_drive_item() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let drive_item = DriveItem {
id: None,
name: Some("new name".into())
};
let response = client
.me()
.drive()
.item(ITEM_ID)
.update_items(&drive_item)
.send()
.await?;
println!("{:#?}", response);
let drive_item: DriveItem = response.json().await?;
println!("{:#?}", drive_item);
let response = client
.me()
.drive()
.item(ITEM_ID)
.get_items()
.send()
.await?;
println!("{:#?}", response);
let drive_item: DriveItem = response.json().await?;
println!("{:#?}", drive_item);
Ok(())
}
分页
分页处理了响应体是数据截断版本且提供了一个URL以继续调用并获取剩余数据的场景。分页可以包含调用链中的多个链接。
SDK提供了方便的方法来获取分页场景中的所有数据,例如使用下一个链接或使用delta链接跟踪Graph数据的更改。
如果您只需要快速轻松地获取所有下一个链接响应或JSON体,可以使用paging().json()
方法,该方法将耗尽所有下一个链接调用并返回所有响应的VecDeque<Response<Result<T>>>
。请注意,需要调用的下一个链接调用量越大,调用此方法的返回延迟就越长。
所有分页方法都会读取响应体以获取调用下一个链接请求的@odata.nextLink
URL。因此,原始的reqwest::Response
丢失了。然而,分页响应被重新包装在类似于reqwest::Response
的Response对象(http::Response
)中。
有关在SDK中使用分页调用的更多信息,请参阅分页详细指南。
对于分页Microsoft Graph API,支持级别不同。有关受支持的API和可用性的更多信息,请参阅文档在您的应用程序中分页Microsoft Graph数据。
#[macro_use]
extern crate serde;
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub(crate) id: Option<String>,
#[serde(rename = "userPrincipalName")]
user_principal_name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Users {
pub value: Vec<User>,
}
async fn paging() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let deque = client
.users()
.list_user()
.select(&["id", "userPrincipalName"])
.paging()
.json::<Users>()
.await?;
println!("{:#?}", deque);
Ok(())
}
分页示例显示了列出用户并调用所有下一个链接的简单方法。您还可以流式传输下一个链接响应或使用通道接收器来获取响应。
流式传输
仅通过异步客户端可用。
use futures::StreamExt;
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
pub async fn stream_next_links() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let mut stream = client
.users()
.list_user()
.select(&["id", "userPrincipalName"])
.paging()
.stream::<serde_json::Value>()?;
while let Some(result) = stream.next().await {
let response = result?;
println!("{:#?}", response);
let body = response.into_body();
println!("{:#?}", body);
}
Ok(())
}
pub async fn stream_delta() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let mut stream = client
.users()
.delta()
.paging()
.stream::<serde_json::Value>()?;
while let Some(result) = stream.next().await {
let response = result?;
println!("{:#?}", response);
let body = response.into_body();
println!("{:#?}", body);
}
Ok(())
}
通道
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
async fn channel_next_links() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let mut receiver = client
.users()
.list_user()
.paging()
.channel::<serde_json::Value>()
.await?;
while let Some(result) = receiver.recv().await {
match result {
Ok(response) => {
println!("{:#?}", response);
let body = response.into_body();
println!("{:#?}", body);
}
Err(err) => panic!("{:#?}", err),
}
}
Ok(())
}
使用实现std::io::Read或tokio::io::AsyncReadExt的类型
文件类型
如果您熟悉使用reqwest
库,可以使用reqwest::Body
和reqwest::blocking::Body
来处理文件。
- 您可以直接传递
std::fs::File
和tokio::fs::File
。
use graph_rs_sdk::GraphClient;
fn use_file_directly(file: File) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let _ = client
.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(file)
.into_blocking()
.send()?;
Ok(())
}
async fn use_async_file_directly(file: tokio::fs::File) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let _ = client
.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(file)
.send()
.await?;
Ok(())
}
reqwest::Body
类型被重导出为graph_rs_sdk::http::Body
,而reqwest::blocking::Body
类型被重导出为graph_rs_sdk::http::blocking::Body
。
use graph_rs_sdk::GraphClient;
use graph_rs_sdk::http::{Body, blocking};
fn use_reqwest_for_files(file: std::fs::File) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let _ = client.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(blocking::Body::from(file))
.into_blocking()
.send()?;
Ok(())
}
async fn use_reqwest_for_async_files(file: tokio::fs::File) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let _ = client.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(Body::from(file))
.send()
.await?;
Ok(())
}
- 如果您想直接使用 reqwest 包
fn use_reqwest_for_files(file: File) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let _ = client
.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(reqwest::blocking::Body::from(file))
.into_blocking()
.send()?;
Ok(())
}
async fn use_reqwest_for_tokio_files(file: tokio::fs::File) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let _ = client
.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(reqwest::Body::from(file))
.send()
.await?;
Ok(())
}
其他实现了 std::io::Read 或 tokio::io::AsyncReadExt 的类型
您可以使用辅助类型 BodyRead
use graph_rs_sdk::http::BodyRead;
use graph_rs_sdk::GraphClient;
fn use_read(reader: impl std::io::Read) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let body_read = BodyRead::from_read(reader)?;
let _ = client
.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(body_read)
.into_blocking()
.send()?;
Ok(())
}
async fn use_async_read(
async_reader: impl tokio::io::AsyncReadExt + Unpin,
) -> anyhow::Result<()> {
let client = GraphClient::new("token");
let async_body_read = BodyRead::from_async_read(async_reader).await?;
let _ = client
.drive("drive-id")
.item_by_path(":/drive/path:")
.update_items_content(async_body_read)
.send()
.await?;
Ok(())
}
API 使用
以下展示了如何使用客户端和一些 API 的几个示例。
OneDrive
使用 drive id 或通过为我、网站、用户和组指定的特定驱动来向驱动发送请求。
use graph_rs_sdk::*;
async fn drives() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.drives()
.list_drive()
.send()
.await
.unwrap();
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
let response = client
.drive("DRIVE-ID")
.item("ITEM_ID")
.get_items()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
Me API
async fn drive_me() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.me()
.drive()
.item("ITEM_ID")
.get_items()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
用户 API
async fn drive_users() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.user("USER_ID")
.drive()
.item("ITEM_ID")
.get_items()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
网站 API
async fn drive_users() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.site("SITE_ID")
.drive()
.item("ITEM_ID")
.get_items()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
创建文件夹。
use graph_rs_sdk::*;
use std::collections::HashMap;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
static FOLDER_NAME: &str = "NEW_FOLDER_NAME";
static PARENT_ID: &str = "PARENT_ID";
// For more info on creating a folder see:
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online
pub async fn create_new_folder() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let folder: HashMap<String, serde_json::Value> = HashMap::new();
let response = client
.me()
.drive()
.item(PARENT_ID)
.create_children(&serde_json::json!({
"name": FOLDER_NAME,
"folder": folder,
"@microsoft.graph.conflictBehavior": "fail"
}))
.send()
.await?;
println!("{:#?}", response);
Ok(())
}
针对驱动器的路径地址。
// Pass the path location of the item staring from the OneDrive root folder.
// Start the path with :/ and end with :
async fn get_item_by_path() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let response = client
.me()
.drive()
.item_by_path(":/documents/document.docx:")
.get_items()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
邮件
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
async fn get_mail_folder() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let response = client.me()
.mail_folder(MAIL_FOLDER_ID)
.get_mail_folders()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await.unwrap();
println!("{:#?}", body);
Ok(())
}
创建消息
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
static MAIL_FOLDER_ID: &str = "MAIL_FOLDER_ID";
async fn create_message() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let response = client
.me()
.messages()
.create_messages(&serde_json::json!({
"subject":"Did you see last night's game?",
"importance":"Low",
"body":{
"contentType":"HTML",
"content":"They were <b>awesome</b>!"
},
"toRecipients":[
{
"emailAddress":{
"address":"[email protected]"
}
}
]
}))
.send()
.await?;
println!("{:#?}", response);
Ok(())
}
发送邮件
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
async fn send_mail() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let response = client
.me()
.send_mail(&serde_json::json!({
"message": {
"subject": "Meet for lunch?",
"body": {
"contentType": "Text",
"content": "The new cafeteria is open."
},
"toRecipients": [
{
"emailAddress": {
"address": "[email protected]"
}
}
],
"ccRecipients": [
{
"emailAddress": {
"address": "[email protected]"
}
}
]
},
"saveToSentItems": "false"
}))
.send()
.await?;
println!("{:#?}", response);
Ok(())
}
邮件文件夹
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
static MAIL_FOLDER_ID: &str = "MAIL_FOLDER_ID";
async fn create_mail_folder_message() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let response = client
.me()
.mail_folder(MAIL_FOLDER_ID)
.messages()
.create_messages(&serde_json::json!({
"subject":"Did you see last night's game?",
"importance":"Low",
"body": {
"contentType":"HTML",
"content":"They were <b>awesome</b>!"
},
"toRecipients":[{
"emailAddress":{
"address":"[email protected]"
}
}
]
}))
.send()
.await?;
println!("{:#?}", response);
Ok(())
}
获取收件箱消息
use graph_rs_sdk::*;
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
static USER_ID: &str = "USER_ID";
async fn get_user_inbox_messages() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let response = client
.user(USER_ID)
.mail_folder("Inbox")
.messages()
.list_messages()
.top("2")
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
使用您自己的结构体。任何实现了 serde::Serialize 的内容都可以用于创建邮件消息或为 OneDrive 创建文件夹。
#[macro_use]
extern crate serde;
use graph_rs_sdk::*;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Message {
subject: String,
importance: String,
body: HashMap<String, String>,
#[serde(rename = "toRecipients")]
to_recipients: Vec<ToRecipient>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct ToRecipient {
#[serde(rename = "emailAddress")]
email_address: EmailAddress,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct EmailAddress {
address: String,
}
async fn create_message() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
let mut body: HashMap<String, String> = HashMap::new();
body.insert("contentType".to_string(), "HTML".to_string());
body.insert("content".to_string(), "They were <b>awesome</b>!".to_string());
let message = Message {
subject: "Did you see last night's game?".into(),
importance: "Low".into(),
body,
to_recipients: vec![
ToRecipient {
email_address: EmailAddress {
address : "[email protected]".into()
}
}
]
};
let response = client
.me()
.messages()
.create_messages(&message)
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
OData 查询
use graph_rs_sdk::*;
async fn create_message() -> GraphResult<()> {
let client = GraphClient::new("ACCESS_TOKEN");
// Get all files in the root of the drive
// and select only specific properties.
let response = client
.me()
.drive()
.get_drive()
.select(&["id", "name"])
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
批量请求
在单个请求中调用多个 Graph API。
use graph_rs_sdk::*;
static USER_ID: &str = "USER_ID";
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
async fn batch() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let json = serde_json::json!({
"requests": [
{
"id": "1",
"method": "GET",
"url": format!("/users/{USER_ID}/drive")
},
{
"id": "2",
"method": "GET",
"url": format!("/users/{USER_ID}/drive/root")
},
{
"id": "3",
"method": "GET",
"url": format!("/users/{USER_ID}/drive/recent")
},
{
"id": "4",
"method": "GET",
"url": format!("/users/{USER_ID}/drive/root/children")
},
{
"id": "5",
"method": "GET",
"url": format!("/users/{USER_ID}/drive/special/documents")
},
]
});
let response = client.batch(&json).send().await?;
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
Id 对比非 Id 方法(例如 user("user-id")
与 users()
)
许多可用的 API 都有不需要资源 id 的方法,同时许多 API 也有需要 id 的方法。对于大多数这些资源,该 SDK 使用两种不同的命名方案实现了这些方法,允许用户直接访问他们想要的方法。
例如,用户 API 可以列出没有 id 的用户,您可以通过调用 users()
方法找到 list_users()
,而获取特定用户则需要用户 id,您可以通过调用 user<ID: AsRef<str>>(id: ID)
方法找到 get_users()
。
使用 users()
方法
use graph_rs_sdk::*;
// For more info on users see: https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0
// For more examples see the examples directory on GitHub.
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
async fn list_users() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let response = client
.users()
.list_user()
.send()
.await
.unwrap();
println!("{:#?}", response);
let body: serde_json::Value = response.json().await.unwrap();
println!("{:#?}", body);
Ok(())
}
使用用户 id user<ID: AsRef<str>>(id: ID)
方法
use graph_rs_sdk::*;
// For more info on users see: https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0
// For more examples see the examples directory on GitHub
static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
static USER_ID: &str = "USER_ID";
async fn get_user() -> GraphResult<()> {
let client = GraphClient::new(ACCESS_TOKEN);
let response = client
.user(USER_ID)
.get_user()
.send()
.await?;
println!("{:#?}", response);
let body: serde_json::Value = response.json().await?;
println!("{:#?}", body);
Ok(())
}
OAuth - 获取访问令牌
使用应用程序构建器存储您的认证配置,并由客户端处理访问令牌请求。
支持
- OpenId、授权码授权、客户端凭证、设备代码、证书认证
- 自动刷新令牌
- 交互式认证 | features =
interactive-auth
- 设备代码轮询
- 使用证书进行授权 | features =
openssl
详细示例
构建您选择的OAuth或OpenId Connect流主要有两种类型。
PublicClientApplication
ConfidentialClientApplication
一旦构建了ConfidentialClientApplication
或PublicClientApplication
,可以将它们传递给图形客户端。
自动令牌刷新也是通过将ConfidentialClientApplication
或PublicClientApplication
传递给GraphClient
客户端来完成的。
更多示例,请参阅GitHub上examples/oauth
目录中的OAuth示例,地址为GitHub。
fn build_client(confidential_client: ConfidentialClientApplication<ClientSecretCredential>) {
let graph_client = GraphClient::from(&confidential_client);
}
身份平台支持
以下是从Microsoft Identity Platform支持的流
对于需要使用登录后重定向的授权码的流,您可以使用
凭证
授权码授予
授权码授予被认为是机密客户端(除了混合流之外),我们可以通过用户执行登录后重定向的URL查询中返回的授权码来获取访问令牌。
一旦您有了授权码,就可以将其传递给客户端,客户端将在您发出的第一个图形API调用时执行获取访问令牌的请求。
授权码密钥
use graph_rs_sdk::{
GraphClient,
oauth::ConfidentialClientApplication,
};
async fn build_client(
authorization_code: &str,
client_id: &str,
client_secret: &str,
redirect_uri: url::Url,
scope: Vec<&str>
) -> anyhow::Result<GraphClient> {
let mut confidential_client = ConfidentialClientApplication::builder(client_id)
.with_authorization_code(authorization_code) // returns builder type for AuthorizationCodeCredential
.with_client_secret(client_secret)
.with_scope(scope)
.with_redirect_uri(redirect_uri)
.build();
let graph_client = GraphClient::from(confidential_client);
Ok(graph_client)
}
带有证明密钥代码交换的授权码密钥
use graph_rs_sdk::identity::{
AuthorizationCodeCredential, ConfidentialClientApplication, GenPkce,
ProofKeyCodeExchange, TokenCredentialExecutor,
};
use lazy_static::lazy_static;
use url::Url;
// You can also pass your own values for PKCE instead of automatic generation by
// calling ProofKeyCodeExchange::new(code_verifier, code_challenge, code_challenge_method)
lazy_static! {
static ref PKCE: ProofKeyCodeExchange = ProofKeyCodeExchange::oneshot().unwrap();
}
fn authorization_sign_in_url(client_id: &str, redirect_uri: url::Url, scope: Vec<String>) -> anyhow::Result<Url> {
Ok(AuthorizationCodeCredential::authorization_url_builder(client_id)
.with_scope(scope)
.with_redirect_uri(redirect_uri)
.with_pkce(&PKCE)
.url()?)
}
fn build_confidential_client(
authorization_code: &str,
client_id: &str,
client_secret: &str,
redirect_uri: url::Url,
scope: Vec<String>,
) -> anyhow::Result<ConfidentialClientApplication<AuthorizationCodeCredential>> {
Ok(ConfidentialClientApplication::builder(client_id)
.with_auth_code(authorization_code)
.with_client_secret(client_secret)
.with_scope(scope)
.with_redirect_uri(redirect_uri)
.with_pkce(&PKCE)
.build())
}
客户端凭证
OAuth 2.0客户端凭据授予流程允许一个网络服务(机密客户端)使用其自己的凭据,而不是模拟用户,在调用另一个网络服务时进行身份验证。在RFC 6749中指定的授予,有时称为双腿OAuth,可以使用应用程序的身份来访问托管在Web上的资源。此类型通常用于在后台运行、无需立即与用户交互的服务器到服务器的交互,通常称为守护进程或服务帐户。
客户端凭据流程需要一次性管理员接受应用程序作用域的权限。要查看构建登录URL并作为管理员接受权限的示例,请参阅管理员同意示例
客户端密钥凭证
use graph_rs_sdk::{
oauth::ConfidentialClientApplication, GraphClient
};
pub async fn get_graph_client(tenant: &str, client_id: &str, client_secret: &str) -> GraphClient {
let mut confidential_client_application = ConfidentialClientApplication::builder(client_id)
.with_client_secret(client_secret)
.with_tenant(tenant)
.build();
GraphClient::from(&confidential_client_application)
}
环境凭证
客户端密钥环境凭证
环境变量
- AZURE_TENANT_ID(可选/推荐 - 在授权URL中放置租户ID)
- AZURE_CLIENT_ID(必需)
- AZURE_CLIENT_SECRET(必需)
pub fn client_secret_credential() -> anyhow::Result<GraphClient> {
let confidential_client = EnvironmentCredential::client_secret_credential()?;
Ok(GraphClient::from(&confidential_client))
}
资源所有者密码凭证
环境变量
- AZURE_TENANT_ID(可选 - 在授权URL中放置租户ID)
- AZURE_CLIENT_ID(必需)
- AZURE_USERNAME(必需)
- AZURE_PASSWORD(必需)
pub fn username_password() -> anyhow::Result<GraphClient> {
let public_client = EnvironmentCredential::resource_owner_password_credential()?;
Ok(GraphClient::from(&public_client))
}
自动刷新令牌
客户端使用内存缓存存储令牌。有关其他持久化机制,请参阅令牌持久化机制开发
使用自动令牌刷新需要从令牌响应中获取刷新令牌。要获取刷新令牌,您必须包含offline_access
作用域。
自动令牌刷新是通过将ConfidentialClientApplication
或PublicClientApplication
传递给GraphClient
客户端来完成的。
如果您使用的是client_credentials
授予,则不需要offline_access
作用域。令牌仍会自动刷新,因为此流程不需要使用刷新令牌来获取新的访问令牌。
以下示例使用授权码授予。
首先创建用户登录的URL。用户登录后,将被重定向回您的应用程序,并且认证代码将包含在uri的查询中。
pub fn authorization_sign_in_url(client_id: &str, tenant: &str, redirect_uri: url::Url) -> Url {
let scope = vec!["offline_access"];
AuthorizationCodeCredential::authorization_url_builder(client_id)
.with_redirect_uri(redirect_uri)
.with_scope(scope)
.url()
.unwrap()
}
一旦您获得授权代码,就可以构建一个机密客户端并将其传递给图形客户端。
async fn build_client(
authorization_code: &str,
client_id: &str,
client_secret: &str,
scope: Vec<String>, // with offline_access
redirect_uri: url::Url,
) -> anyhow::Result<GraphClient> {
let mut confidential_client = ConfidentialClientApplication::builder(client_id)
.with_auth_code(authorization_code) // returns builder type for AuthorizationCodeCredential
.with_client_secret(client_secret)
.with_scope(scope)
.with_redirect_uri(redirect_uri)
.build();
let graph_client = GraphClient::from(&confidential_client);
Ok(graph_client)
}
令牌持久化机制开发
当前仅提供内存中的令牌缓存用于令牌持久化。正在积极开发其他持久化机制,例如Azure Key Vault和桌面机制(如MacOS KeyChain),这些将在2.0.0版本之后发布。您可以在https://github.com/sreeise/graph-rs-sdk/issues/432跟踪此进度。
交互式认证
需要功能 interactive-auth
警告:在某些场景下,在异步上下文中运行交互式认证可能会导致崩溃。我们建议进行彻底测试,以确保您能够为您的用例使用交互式认证。此外,设备代码交互式认证目前不支持在异步代码中。我们正在努力在2.0.0版本后解决这个问题。
[dependencies]
graph-rs-sdk = { version = "...", features = ["interactive-auth"] }
交互式认证使用wry crate在支持的平台(如桌面)上运行网页视图。
use graph_rs_sdk::{identity::{AuthorizationCodeCredential, Secret}, GraphClient};
async fn authenticate(
tenant_id: &str,
client_id: &str,
client_secret: &str,
redirect_uri: url::Url,
scope: Vec<&str>,
) -> anyhow::Result<GraphClient> {
std::env::set_var("RUST_LOG", "debug");
pretty_env_logger::init();
let (authorization_query_response, mut credential_builder) =
AuthorizationCodeCredential::authorization_url_builder(client_id)
.with_tenant(tenant_id)
.with_scope(scope) // Adds offline_access as a scope which is needed to get a refresh token.
.with_redirect_uri(redirect_uri)
.with_interactive_auth(Secret("client-secret".to_string()), Default::default())
.unwrap();
debug!("{authorization_query_response:#?}");
let mut confidential_client = credential_builder.with_client_secret(client_secret).build();
Ok(GraphClient::from(&confidential_client))
}
贡献
请参阅GitHub上的贡献指南
维基
请参阅GitHub Wiki
功能请求或错误报告
对于错误报告,请在GitHub上提交一个问题,我们将尽快给出响应或修复。
GitHub上的讨论标签已启用,所以请随时前往那里提出任何问题或功能请求。对于错误,请先提交问题。功能可以通过问题或讨论请求。两种方式都适用。除此之外,您可以随时提问,为他人提供技巧,并就项目进行一般讨论。
依赖关系
~22–63MB
~1M SLoC