Skip to main content

casdoor_rust_sdk/
user.rs

1// Copyright 2026 The Casdoor Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19
20use crate::client::{get_owner, Client, PostBody, Response};
21use crate::error::Result;
22use crate::order::ProductInfo;
23use crate::organization::MfaItem;
24use crate::permission::Permission;
25use crate::role::Role;
26use crate::serde_util::null_to_default;
27
28/// The name of the session that holds the MFA recovery codes.
29pub const MFA_RECOVERY_CODES_SESSION: &str = "mfa_recovery_codes";
30
31#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase", default)]
33pub struct ManagedAccount {
34    pub application: String,
35    pub username: String,
36    pub password: String,
37    pub signin_url: String,
38}
39
40#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase", default)]
42pub struct MfaAccount {
43    pub account_name: String,
44    pub issuer: String,
45    pub secret_key: String,
46    pub origin: String,
47}
48
49#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase", default)]
51pub struct Address {
52    pub tag: String,
53    pub line1: String,
54    pub line2: String,
55    pub city: String,
56    pub state: String,
57    pub zip_code: String,
58    pub region: String,
59}
60
61#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase", default)]
63pub struct FaceId {
64    pub name: String,
65    #[serde(deserialize_with = "null_to_default")]
66    pub face_id_data: Vec<f64>,
67    pub image_url: String,
68}
69
70#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
71#[serde(rename_all = "camelCase", default)]
72pub struct MfaProps {
73    pub enabled: bool,
74    pub is_preferred: bool,
75    pub mfa_type: String,
76    pub secret: String,
77    pub country_code: String,
78    pub url: String,
79    #[serde(deserialize_with = "null_to_default")]
80    pub recovery_codes: Vec<String>,
81    pub mfa_remember_in_hours: i32,
82}
83
84/// The user info returned by the OIDC `/api/userinfo` API.
85#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
86#[serde(default)]
87pub struct Userinfo {
88    pub sub: String,
89    pub iss: String,
90    pub aud: String,
91    #[serde(rename = "preferred_username")]
92    pub name: String,
93    #[serde(rename = "name")]
94    pub display_name: String,
95    pub email: String,
96    pub email_verified: bool,
97    #[serde(rename = "picture")]
98    pub avatar: String,
99    pub address: String,
100    pub phone: String,
101    pub real_name: String,
102    pub is_verified: bool,
103    #[serde(deserialize_with = "null_to_default")]
104    pub groups: Vec<String>,
105    #[serde(deserialize_with = "null_to_default")]
106    pub roles: Vec<String>,
107    #[serde(deserialize_with = "null_to_default")]
108    pub permissions: Vec<String>,
109}
110
111#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase", default)]
113pub struct ConsentRecord {
114    /// The `owner/name` ID of the application.
115    pub application: String,
116    #[serde(deserialize_with = "null_to_default")]
117    pub granted_scopes: Vec<String>,
118}
119
120#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase", default)]
122pub struct ThirdPartyLink {
123    pub owner: String,
124    pub user_name: String,
125    pub provider_name: String,
126    pub provider_id: String,
127    pub created_time: String,
128}
129
130#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase", default)]
132pub struct ScopeDescription {
133    pub scope: String,
134    pub display_name: String,
135    pub description: String,
136}
137
138/// User has the same definition as
139/// <https://github.com/casdoor/casdoor/blob/master/object/user.go>.
140#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase", default)]
142pub struct User {
143    pub owner: String,
144    pub name: String,
145    pub created_time: String,
146    pub updated_time: String,
147    pub deleted_time: String,
148
149    pub id: String,
150    pub external_id: String,
151    pub r#type: String,
152    pub password: String,
153    pub password_salt: String,
154    pub password_type: String,
155    pub display_name: String,
156    pub first_name: String,
157    pub last_name: String,
158    pub avatar: String,
159    pub avatar_type: String,
160    pub permanent_avatar: String,
161    pub email: String,
162    pub email_verified: bool,
163    pub phone: String,
164    pub country_code: String,
165    pub region: String,
166    pub location: String,
167    #[serde(deserialize_with = "null_to_default")]
168    pub address: Vec<String>,
169    #[serde(deserialize_with = "null_to_default")]
170    pub addresses: Vec<Address>,
171    pub affiliation: String,
172    pub title: String,
173    pub id_card_type: String,
174    pub id_card: String,
175    pub real_name: String,
176    pub is_verified: bool,
177    pub homepage: String,
178    pub bio: String,
179    pub tag: String,
180    pub language: String,
181    pub gender: String,
182    pub birthday: String,
183    pub education: String,
184    pub score: i32,
185    pub karma: i32,
186    pub ranking: i32,
187    pub balance: f64,
188    pub balance_credit: f64,
189    pub currency: String,
190    pub balance_currency: String,
191    pub is_default_avatar: bool,
192    pub is_online: bool,
193    pub is_admin: bool,
194    pub is_forbidden: bool,
195    pub is_deleted: bool,
196    pub signup_application: String,
197    pub hash: String,
198    pub pre_hash: String,
199    pub register_type: String,
200    pub register_source: String,
201    pub access_token: String,
202    pub original_token: String,
203    pub original_refresh_token: String,
204
205    pub created_ip: String,
206    pub last_signin_time: String,
207    pub last_signin_ip: String,
208
209    pub github: String,
210    pub google: String,
211    pub qq: String,
212    pub wechat: String,
213    pub facebook: String,
214    pub dingtalk: String,
215    pub weibo: String,
216    pub gitee: String,
217    pub linkedin: String,
218    pub wecom: String,
219    pub lark: String,
220    pub gitlab: String,
221    pub adfs: String,
222    pub baidu: String,
223    pub alipay: String,
224    pub casdoor: String,
225    pub infoflow: String,
226    pub apple: String,
227    pub azuread: String,
228    pub azureadb2c: String,
229    pub slack: String,
230    pub steam: String,
231    pub bilibili: String,
232    pub okta: String,
233    pub douyin: String,
234    pub kwai: String,
235    pub line: String,
236    pub amazon: String,
237    pub auth0: String,
238    pub battlenet: String,
239    pub bitbucket: String,
240    #[serde(rename = "box")]
241    pub r#box: String,
242    pub cloudfoundry: String,
243    pub dailymotion: String,
244    pub deezer: String,
245    pub digitalocean: String,
246    pub discord: String,
247    pub dropbox: String,
248    pub eveonline: String,
249    pub fitbit: String,
250    pub gitea: String,
251    pub heroku: String,
252    pub influxcloud: String,
253    pub instagram: String,
254    pub intercom: String,
255    pub kakao: String,
256    pub lastfm: String,
257    pub mailru: String,
258    pub meetup: String,
259    pub microsoftonline: String,
260    pub naver: String,
261    pub nextcloud: String,
262    pub onedrive: String,
263    pub oura: String,
264    pub patreon: String,
265    pub paypal: String,
266    pub salesforce: String,
267    pub shopify: String,
268    pub soundcloud: String,
269    pub spotify: String,
270    pub strava: String,
271    pub stripe: String,
272    pub telegram: String,
273    pub tiktok: String,
274    pub tumblr: String,
275    pub twitch: String,
276    pub twitter: String,
277    pub typetalk: String,
278    pub uber: String,
279    pub vk: String,
280    pub wepay: String,
281    pub xero: String,
282    pub yahoo: String,
283    pub yammer: String,
284    pub yandex: String,
285    pub zoom: String,
286    pub metamask: String,
287    pub web3onboard: String,
288    pub oidc: String,
289    pub custom: String,
290    pub custom2: String,
291    pub custom3: String,
292    pub custom4: String,
293    pub custom5: String,
294    pub custom6: String,
295    pub custom7: String,
296    pub custom8: String,
297    pub custom9: String,
298    pub custom10: String,
299
300    /// The WebAuthn credentials of the user, kept as raw JSON so that the value is
301    /// round-tripped untouched on `get_user()` and `update_user()`.
302    pub webauthn_credentials: Value,
303    pub preferred_mfa_type: String,
304    #[serde(deserialize_with = "null_to_default")]
305    pub recovery_codes: Vec<String>,
306    pub totp_secret: String,
307    pub mfa_phone_enabled: bool,
308    pub mfa_email_enabled: bool,
309    pub mfa_radius_enabled: bool,
310    pub mfa_radius_username: String,
311    pub mfa_radius_provider: String,
312    pub mfa_push_enabled: bool,
313    pub mfa_push_receiver: String,
314    pub mfa_push_provider: String,
315    #[serde(deserialize_with = "null_to_default")]
316    pub multi_factor_auths: Vec<MfaProps>,
317    pub invitation: String,
318    pub invitation_code: String,
319    #[serde(deserialize_with = "null_to_default")]
320    pub face_ids: Vec<FaceId>,
321    #[serde(deserialize_with = "null_to_default")]
322    pub cart: Vec<ProductInfo>,
323
324    pub ldap: String,
325    pub uid_number: i32,
326    #[serde(deserialize_with = "null_to_default")]
327    pub properties: HashMap<String, String>,
328
329    #[serde(deserialize_with = "null_to_default")]
330    pub third_party_links: Vec<ThirdPartyLink>,
331
332    #[serde(deserialize_with = "null_to_default")]
333    pub roles: Vec<Role>,
334    #[serde(deserialize_with = "null_to_default")]
335    pub permissions: Vec<Permission>,
336    #[serde(deserialize_with = "null_to_default")]
337    pub groups: Vec<String>,
338
339    pub last_change_password_time: String,
340    pub last_signin_wrong_time: String,
341    pub signin_wrong_times: i32,
342
343    #[serde(deserialize_with = "null_to_default")]
344    pub managed_accounts: Vec<ManagedAccount>,
345    #[serde(deserialize_with = "null_to_default")]
346    pub mfa_accounts: Vec<MfaAccount>,
347    #[serde(deserialize_with = "null_to_default")]
348    pub mfa_items: Vec<MfaItem>,
349    pub mfa_remember_deadline: String,
350    pub need_update_password: bool,
351    pub ip_whitelist: String,
352    #[serde(deserialize_with = "null_to_default")]
353    pub application_scopes: Vec<ConsentRecord>,
354}
355
356impl User {
357    /// Return the Casdoor object ID (`"owner/name"`) of the user.
358    pub fn get_id(&self) -> String {
359        format!("{}/{}", self.owner, self.name)
360    }
361}
362
363impl Client {
364    /// Get the users of all the organizations.
365    pub async fn get_global_users(&self) -> Result<Vec<User>> {
366        self.get_list("get-global-users", &[]).await
367    }
368
369    /// Get the users of the client's organization.
370    pub async fn get_users(&self) -> Result<Vec<User>> {
371        self.get_list("get-users", &[("owner", &self.config.organization_name)])
372            .await
373    }
374
375    /// Get the users of the client's organization, sorted by `sorter` and limited to
376    /// `limit` results.
377    pub async fn get_sorted_users(&self, sorter: &str, limit: i32) -> Result<Vec<User>> {
378        self.get_list(
379            "get-sorted-users",
380            &[
381                ("owner", &self.config.organization_name),
382                ("sorter", sorter),
383                ("limit", &limit.to_string()),
384            ],
385        )
386        .await
387    }
388
389    /// Get one page of the users of the client's organization, together with the total
390    /// number of the users.
391    pub async fn get_pagination_users(
392        &self,
393        p: i32,
394        page_size: i32,
395        query: &[(&str, &str)],
396    ) -> Result<(Vec<User>, i32)> {
397        self.get_pagination(
398            "get-users",
399            &self.config.organization_name,
400            p,
401            page_size,
402            query,
403        )
404        .await
405    }
406
407    /// Get the number of the users of the client's organization. `is_online` can be `""`
408    /// for all the users, `"1"` for the online ones and `"0"` for the offline ones.
409    pub async fn get_user_count(&self, is_online: &str) -> Result<i32> {
410        let url = self.get_url(
411            "get-user-count",
412            &[
413                ("owner", &self.config.organization_name),
414                ("isOnline", is_online),
415            ],
416        );
417
418        let response = self.do_get_response(&url).await?;
419        Ok(serde_json::from_value(response.data)?)
420    }
421
422    /// Get one user by name, or by the `owner/name` ID for a user of another
423    /// organization.
424    pub async fn get_user(&self, name: &str) -> Result<Option<User>> {
425        self.get_object("get-user", &[("id", &self.get_id(name))])
426            .await
427    }
428
429    /// Get the user that the client is authenticated as, by calling the
430    /// `/api/get-account` API. It's meant to be used with a client returned by
431    /// [`Client::with_access_token()`], so that the owner of an access token can be
432    /// retrieved.
433    pub async fn get_account(&self) -> Result<Option<User>> {
434        self.get_object("get-account", &[]).await
435    }
436
437    /// Get one user of the client's organization by email.
438    pub async fn get_user_by_email(&self, email: &str) -> Result<Option<User>> {
439        self.get_object(
440            "get-user",
441            &[("owner", &self.config.organization_name), ("email", email)],
442        )
443        .await
444    }
445
446    /// Get one user of the client's organization by phone.
447    pub async fn get_user_by_phone(&self, phone: &str) -> Result<Option<User>> {
448        self.get_object(
449            "get-user",
450            &[("owner", &self.config.organization_name), ("phone", phone)],
451        )
452        .await
453    }
454
455    /// Get one user of the client's organization by the user's `id` field.
456    pub async fn get_user_by_user_id(&self, user_id: &str) -> Result<Option<User>> {
457        self.get_object(
458            "get-user",
459            &[
460                ("owner", &self.config.organization_name),
461                ("userId", user_id),
462            ],
463        )
464        .await
465    }
466
467    /// Set the password of a user. `old_password` is not required, just pass an empty
468    /// string when you don't need it.
469    pub async fn set_password(
470        &self,
471        owner: &str,
472        name: &str,
473        old_password: &str,
474        new_password: &str,
475    ) -> Result<bool> {
476        let fields = HashMap::from([
477            ("userOwner".to_string(), owner.to_string()),
478            ("userName".to_string(), name.to_string()),
479            ("oldPassword".to_string(), old_password.to_string()),
480            ("newPassword".to_string(), new_password.to_string()),
481        ]);
482
483        let response = self
484            .do_post("set-password", &[], PostBody::Form(fields))
485            .await?;
486
487        Ok(response.status == "ok")
488    }
489
490    /// Add a user.
491    pub async fn add_user(&self, user: &User) -> Result<bool> {
492        Ok(self.modify_user("add-user", user, &[]).await?.1)
493    }
494
495    /// Update a user.
496    pub async fn update_user(&self, user: &User) -> Result<bool> {
497        Ok(self.modify_user("update-user", user, &[]).await?.1)
498    }
499
500    /// Update only the given columns of a user.
501    pub async fn update_user_for_columns(&self, user: &User, columns: &[&str]) -> Result<bool> {
502        Ok(self.modify_user("update-user", user, columns).await?.1)
503    }
504
505    /// Update the user addressed by the given `owner/name` ID.
506    pub async fn update_user_by_id(&self, id: &str, user: &User) -> Result<bool> {
507        Ok(self
508            .modify_user_by_id("update-user", id, user, &[])
509            .await?
510            .1)
511    }
512
513    /// Update the user of `owner` whose `id` field is `user_id`.
514    pub async fn update_user_by_user_id(
515        &self,
516        owner: &str,
517        user_id: &str,
518        user: &User,
519    ) -> Result<bool> {
520        let post_bytes = serde_json::to_vec(user)?;
521        let response = self
522            .do_post(
523                "update-user",
524                &[("owner", owner), ("userId", user_id)],
525                PostBody::Raw(post_bytes),
526            )
527            .await?;
528
529        Ok(response.data.as_str() == Some("Affected"))
530    }
531
532    /// Delete a user.
533    pub async fn delete_user(&self, user: &User) -> Result<bool> {
534        Ok(self.modify_user("delete-user", user, &[]).await?.1)
535    }
536
537    /// Check whether the `password` field of the given user is the user's password.
538    pub async fn check_user_password(&self, user: &User) -> Result<bool> {
539        let (response, _) = self.modify_user("check-user-password", user, &[]).await?;
540        Ok(response.status == "ok")
541    }
542
543    /// The encapsulation of the user CUD (Create, Update, Delete) operations, the possible
544    /// actions are `add-user`, `update-user`, `delete-user` and `check-user-password`.
545    async fn modify_user(
546        &self,
547        action: &str,
548        user: &User,
549        columns: &[&str],
550    ) -> Result<(Response, bool)> {
551        let owner = get_owner(&user.owner, &self.config.organization_name);
552        let id = format!("{}/{}", owner, user.name);
553
554        self.modify_user_by_id(action, &id, user, columns).await
555    }
556
557    async fn modify_user_by_id(
558        &self,
559        action: &str,
560        id: &str,
561        user: &User,
562        columns: &[&str],
563    ) -> Result<(Response, bool)> {
564        let mut user = user.clone();
565        user.owner = get_owner(&user.owner, &self.config.organization_name);
566
567        self.modify_object(action, id, &user, columns).await
568    }
569}