Skip to main content

rocketmq_auth/authentication/model/
user.rs

1// Copyright 2023 The RocketMQ Rust Authors
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.
14use 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}