rocketmq_auth/authentication/model/
user.rs1use std::fmt;
15use std::sync::OnceLock;
16
17use cheetah_string::CheetahString;
18use serde::Deserialize;
19use serde::Serialize;
20
21use crate::authentication::enums::subject_type::SubjectType;
22use crate::authentication::enums::user_status::UserStatus;
23use crate::authentication::enums::user_type::UserType;
24use crate::authentication::model::subject::Subject;
25
26#[derive(Clone, Serialize, Deserialize)]
27pub struct User {
28 username: CheetahString,
29 password: Option<CheetahString>,
30 #[serde(rename = "userType")]
31 user_type: Option<UserType>,
32 #[serde(rename = "userStatus")]
33 user_status: Option<UserStatus>,
34 #[serde(skip)]
35 subject_key_cache: OnceLock<String>,
36}
37
38impl fmt::Debug for User {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 f.debug_struct("User")
41 .field("username", &self.username)
42 .field("password", &self.password.as_ref().map(|_| "<redacted>"))
43 .field("user_type", &self.user_type)
44 .field("user_status", &self.user_status)
45 .finish_non_exhaustive()
46 }
47}
48
49impl User {
50 pub fn of(username: impl Into<CheetahString>) -> Self {
51 User {
52 username: username.into(),
53 password: None,
54 user_type: None,
55 user_status: None,
56 subject_key_cache: OnceLock::new(),
57 }
58 }
59
60 pub fn of_with_password(username: impl Into<CheetahString>, password: impl Into<CheetahString>) -> Self {
61 User {
62 username: username.into(),
63 password: Some(password.into()),
64 user_type: None,
65 user_status: None,
66 subject_key_cache: OnceLock::new(),
67 }
68 }
69
70 pub fn of_with_type(
71 username: impl Into<CheetahString>,
72 password: impl Into<CheetahString>,
73 user_type: UserType,
74 ) -> Self {
75 User {
76 username: username.into(),
77 password: Some(password.into()),
78 user_type: Some(user_type),
79 user_status: None,
80 subject_key_cache: OnceLock::new(),
81 }
82 }
83}
84
85impl User {
86 pub fn username(&self) -> &CheetahString {
87 &self.username
88 }
89
90 pub fn password(&self) -> Option<&CheetahString> {
91 self.password.as_ref()
92 }
93
94 pub fn user_type(&self) -> Option<UserType> {
95 self.user_type
96 }
97
98 pub fn user_status(&self) -> Option<UserStatus> {
99 self.user_status
100 }
101
102 pub fn set_user_status(&mut self, status: UserStatus) {
103 self.user_status = Some(status);
104 }
105
106 pub fn set_password(&mut self, password: impl Into<CheetahString>) {
107 self.password = Some(password.into());
108 }
109
110 pub fn set_user_type(&mut self, user_type: UserType) {
111 self.user_type = Some(user_type);
112 }
113
114 pub fn username_from_subject_key(subject_key: &str) -> &str {
115 subject_key
116 .split_once(':')
117 .map(|(_, username)| username)
118 .unwrap_or(subject_key)
119 }
120}
121
122impl Subject for User {
123 fn subject_key(&self) -> &str {
124 self.subject_key_cache
125 .get_or_init(|| format!("{}:{}", SubjectType::User.name(), self.username))
126 .as_str()
127 }
128
129 fn subject_type(&self) -> SubjectType {
130 SubjectType::User
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_user_of() {
140 let username = "test_user";
141 let user = User::of(username);
142 assert_eq!(user.username(), &CheetahString::from(username));
143 assert!(user.password().is_none());
144 assert!(user.user_type().is_none());
145 assert!(user.user_status().is_none());
146 }
147
148 #[test]
149 fn test_user_of_with_password() {
150 let username = "test_user";
151 let password = "test_password";
152 let user = User::of_with_password(username, password);
153 assert_eq!(user.username(), &CheetahString::from(username));
154 assert_eq!(user.password(), Some(&CheetahString::from(password)));
155 assert!(user.user_type().is_none());
156 assert!(user.user_status().is_none());
157 }
158
159 #[test]
160 fn test_user_of_with_type() {
161 let username = "test_user";
162 let password = "test_password";
163 let user_type = UserType::Normal;
164 let user = User::of_with_type(username, password, user_type);
165 assert_eq!(user.username(), &CheetahString::from(username));
166 assert_eq!(user.password(), Some(&CheetahString::from(password)));
167 assert_eq!(user.user_type(), Some(user_type));
168 assert!(user.user_status().is_none());
169 }
170
171 #[test]
172 fn test_user_set_user_status() {
173 let mut user = User::of("test_user");
174 user.set_user_status(UserStatus::Enable);
175 assert_eq!(user.user_status(), Some(UserStatus::Enable));
176 }
177
178 #[test]
179 fn test_user_subject_trait() {
180 let username = "test_user";
181 let user = User::of(username);
182 assert_eq!(user.subject_key(), "User:test_user");
183 assert_eq!(user.subject_type(), SubjectType::User);
184 }
185
186 #[test]
187 fn user_debug_redacts_password() {
188 let user = User::of_with_password("alice", "top-secret-password");
189
190 let debug = format!("{user:?}");
191
192 assert!(debug.contains("alice"));
193 assert!(debug.contains("<redacted>"));
194 assert!(!debug.contains("top-secret-password"));
195 }
196}