1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use std::collections::HashMap;
use crate::DingTalk;
use deadpool_redis::redis::cmd;
use serde::{Deserialize, Serialize};
use url::{form_urlencoded, Url};
impl DingTalk {
/// Generate the redirect URL for DingTalk authorization.
///
/// [Documents](https://open.dingtalk.com/document/isvapp/obtain-identity-credentials)
///
/// # Arguments
///
/// * `redirect_uri` - The redirect URI after authorization.
/// * `state` - An optional state string, which is used to prevent CSRF attacks.
///
/// # Returns
///
/// The redirect URL as a string.
pub fn get_redirect_url(&self, redirect_uri: String, state: Option<String>) -> String {
let mut url = Url::parse("https://login.dingtalk.com/oauth2/auth").unwrap();
let query = form_urlencoded::Serializer::new(String::new())
.append_pair("redirect_uri", &redirect_uri)
.append_pair("response_type", "code")
.append_pair("client_id", self.appid.as_ref())
.append_pair("scope", "openid corpid")
.append_pair("state", state.unwrap_or("".to_string()).as_ref())
.append_pair("prompt", "consent")
.finish();
url.set_query(Some(&query));
url.to_string()
}
/// Obtain the access token for the application.
///
/// This asynchronous function sends a POST request to the DingTalk API to obtain the access token
/// for the application. The request includes the authorization code in the query parameters for
/// authentication.
///
/// [Documents](https://open.dingtalk.com/document/isvapp/obtain-identity-credentials)
///
/// # Arguments
///
/// * `code` - The authorization code to obtain the access token.
///
/// # Returns
///
/// Returns a `Result` containing the access token as a string if the request is successful,
/// or an error if the request fails or if the response status is not successful.
///
/// # Errors
///
/// Returns an error if the response status is not successful, or if the request fails.
pub async fn set_app_access_token(
&self,
code: String,
) -> Result<String, Box<dyn std::error::Error>> {
let mut params = HashMap::new();
params.insert("clientId", self.appid.clone());
params.insert("clientSecret", self.app_secret.clone());
params.insert("code", code.clone());
params.insert("refreshToken", "".to_string());
params.insert("grantType", "authorization_code".to_string());
let response = self
.client
.post("https://api.dingtalk.com/v1.0/oauth2/userAccessToken")
.json(¶ms)
.send()
.await?;
if !response.status().is_success() {
return Err(format!("Failed to get access token: {}", response.status()).into());
}
#[derive(Serialize, Deserialize, Debug)]
struct AccessToken {
#[serde(rename = "accessToken")]
pub access_token: String,
#[serde(rename = "refreshToken")]
pub refresh_token: String,
#[serde(rename = "corpId")]
pub corp_id: String,
#[serde(rename = "expireIn")]
pub expire_in: i32,
}
let at = response.json::<AccessToken>().await?;
let mut rdb = self.rdb.get().await.unwrap();
cmd("SET")
.arg(&self.appid)
.arg(serde_json::to_string(&at)?)
.query_async::<()>(&mut rdb)
.await
.unwrap();
Ok(at.corp_id) // 企业corpId
}
/// Get the access token for the application.
///
/// The access token is stored in Redis by calling [set_app_access_token].
///
/// # Returns
///
/// A Result containing the access token as a string if the access token exists, otherwise an error string.
pub async fn get_app_access_token(&self) -> Result<String, Box<dyn std::error::Error>> {
let mut rdb = self.rdb.get().await.unwrap();
let value: Option<String> = cmd("GET")
.arg(&self.appid)
.query_async(&mut rdb)
.await
.unwrap_or(None);
#[derive(Serialize, Deserialize, Debug)]
struct AccessToken {
#[serde(rename = "accessToken")]
pub access_token: String,
#[serde(rename = "refreshToken")]
pub refresh_token: String,
#[serde(rename = "corpId")]
pub corp_id: String,
#[serde(rename = "expireIn")]
pub expire_in: i32,
}
if let Some(bytes) = value {
let value: AccessToken = serde_json::from_str(&bytes).unwrap();
return Ok(value.access_token);
}
Err("Failed to get access token".into())
}
}