Skip to main content

v_common/module/
ticket.rs

1use v_individual_model::onto::datatype::Lang;
2use v_individual_model::onto::individual::Individual;
3use crate::v_api::common_type::ResultCode;
4use chrono::{DateTime, NaiveDateTime, Utc};
5use evmap::ShallowCopy;
6use serde_json::Value;
7use std::hash::{Hash, Hasher};
8use std::mem::ManuallyDrop;
9use std::net::IpAddr;
10
11#[derive(Debug, Clone)]
12pub struct Ticket {
13    pub id: String,
14    /// Uri пользователя
15    pub user_uri: String,
16    /// login пользователя
17    pub user_login: String,
18    /// Код результата, если тикет не валидный != ResultCode.Ok
19    pub result: ResultCode,
20    /// Дата начала действия тикета
21    pub start_time: i64,
22    /// Дата окончания действия тикета
23    pub end_time: i64,
24    pub user_addr: String,
25    /// Authentication method used (e.g., "sms", "password", "oauth")
26    pub auth_method: String,
27    /// Domain or service name (e.g., "Ideas", "Main")
28    pub domain: String,
29    /// Ticket creation initiator (e.g., "@https://optiflow.nn.local/ida/#/")
30    pub initiator: String,
31    /// Authentication origin type
32    pub auth_origin: String,
33}
34
35impl Hash for Ticket {
36    fn hash<H: Hasher>(&self, state: &mut H) {
37        self.id.hash(state);
38    }
39}
40
41impl Eq for Ticket {}
42
43impl PartialEq for Ticket {
44    fn eq(&self, other: &Self) -> bool {
45        self.result == other.result
46            && self.user_uri == other.user_uri
47            && self.id == other.id
48            && self.end_time == other.end_time
49            && self.start_time == other.start_time
50            && self.user_login == other.user_login
51            && self.user_addr == other.user_addr
52            && self.auth_method == other.auth_method
53            && self.domain == other.domain
54            && self.initiator == other.initiator
55            && self.auth_origin == other.auth_origin
56    }
57}
58
59impl ShallowCopy for Ticket {
60    unsafe fn shallow_copy(&self) -> ManuallyDrop<Self> {
61        ManuallyDrop::new(Ticket {
62            id: self.id.clone(),
63            user_uri: self.user_uri.clone(),
64            user_login: self.user_login.clone(),
65            result: self.result,
66            start_time: self.start_time,
67            end_time: self.end_time,
68            user_addr: self.user_addr.clone(),
69            auth_method: self.auth_method.clone(),
70            domain: self.domain.clone(),
71            initiator: self.initiator.clone(),
72            auth_origin: self.auth_origin.clone(),
73        })
74    }
75}
76
77impl Default for Ticket {
78    fn default() -> Self {
79        Ticket {
80            id: String::default(),
81            user_uri: String::default(),
82            user_login: String::default(),
83            result: ResultCode::AuthenticationFailed,
84            start_time: 0,
85            end_time: 0,
86            user_addr: "".to_string(),
87            auth_method: "".to_string(),
88            domain: "".to_string(),
89            initiator: "".to_string(),
90            auth_origin: "".to_string(),
91        }
92    }
93}
94
95impl From<serde_json::Value> for Ticket {
96    fn from(val: Value) -> Self {
97        let mut t = Ticket::default();
98        if let Some(v) = val["id"].as_str() {
99            t.id = v.to_owned();
100        }
101        if let Some(v) = val["user_uri"].as_str() {
102            t.user_uri = v.to_owned();
103        }
104        if let Some(v) = val["user_login"].as_str() {
105            t.user_login = v.to_owned();
106        }
107        if let Some(v) = val["result"].as_i64() {
108            t.result = ResultCode::from_i64(v);
109        }
110        if let Some(v) = val["end_time"].as_i64() {
111            t.end_time = v;
112        }
113        if let Some(v) = val["user_addr"].as_str() {
114            t.user_addr = v.to_owned();
115        }
116        if let Some(v) = val["auth_method"].as_str() {
117            t.auth_method = v.to_owned();
118        }
119        if let Some(v) = val["domain"].as_str() {
120            t.domain = v.to_owned();
121        }
122        if let Some(v) = val["initiator"].as_str() {
123            t.initiator = v.to_owned();
124        }
125        if let Some(v) = val["auth_origin"].as_str() {
126            t.auth_origin = v.to_owned();
127        }
128
129        t
130    }
131}
132
133impl Ticket {
134    pub fn to_individual(&self) -> Individual {
135        let mut ticket_indv = Individual::default();
136
137        ticket_indv.add_string("rdf:type", "ticket:ticket", Lang::none());
138        ticket_indv.set_id(&self.id);
139
140        ticket_indv.add_string("ticket:login", &self.user_login, Lang::none());
141        ticket_indv.add_string("ticket:accessor", &self.user_uri, Lang::none());
142        ticket_indv.add_string("ticket:addr", &self.user_addr, Lang::none());
143
144        let start_time_str = DateTime::<Utc>::from_timestamp(self.start_time, 0).unwrap_or_default().format("%Y-%m-%dT%H:%M:%S%.f").to_string();
145
146        if start_time_str.len() > 28 {
147            ticket_indv.add_string("ticket:when", &start_time_str[0..28], Lang::none());
148        } else {
149            ticket_indv.add_string("ticket:when", &start_time_str, Lang::none());
150        }
151
152        ticket_indv.add_string("ticket:duration", &(self.end_time - self.start_time).to_string(), Lang::none());
153        
154        // Add new fields
155        ticket_indv.add_string("ticket:authMethod", &self.auth_method, Lang::none());
156        ticket_indv.add_string("ticket:domain", &self.domain, Lang::none());
157        ticket_indv.add_string("ticket:initiator", &self.initiator, Lang::none());
158        ticket_indv.add_string("ticket:authOrigin", &self.auth_origin, Lang::none());
159        
160        ticket_indv
161    }
162
163    pub fn update_from_individual(&mut self, src: &mut Individual) {
164        let when = src.get_first_literal("ticket:when");
165        let duration = src.get_first_literal("ticket:duration").unwrap_or_default().parse::<i32>().unwrap_or_default();
166
167        self.id = src.get_id().to_owned();
168        self.user_uri = src.get_first_literal("ticket:accessor").unwrap_or_default();
169        self.user_login = src.get_first_literal("ticket:login").unwrap_or_default();
170        self.user_addr = src.get_first_literal("ticket:addr").unwrap_or_default();
171        
172        // Load new fields from RDF
173        self.auth_method = src.get_first_literal("ticket:authMethod").unwrap_or_default();
174        self.domain = src.get_first_literal("ticket:domain").unwrap_or_default();
175        self.initiator = src.get_first_literal("ticket:initiator").unwrap_or_default();
176        self.auth_origin = src.get_first_literal("ticket:authOrigin").unwrap_or_default();
177
178        if self.user_uri.is_empty() {
179            error!("found a session ticket is not complete, the user can not be found. ticket_id={}", self.id);
180            self.user_uri = String::default();
181            return;
182        }
183
184        if !self.user_uri.is_empty() && (when.is_none() || duration < 10) {
185            error!("found a session ticket is not complete, we believe that the user has not been found.");
186            self.user_uri = String::default();
187            return;
188        }
189        let when = when.unwrap();
190
191        if let Ok(t) = NaiveDateTime::parse_from_str(&when, "%Y-%m-%dT%H:%M:%S%.f") {
192            self.start_time = t.and_utc().timestamp();
193            self.end_time = self.start_time + duration as i64;
194        } else {
195            error!("fail parse field [ticket:when] = {}", when);
196            self.user_uri = String::default();
197        }
198    }
199
200    pub fn is_ticket_valid(&self, addr: &Option<IpAddr>, is_check_addr: bool) -> ResultCode {
201        if is_check_addr {
202            if let Some(a) = addr {
203                if self.user_addr != a.to_string() {
204                    error!("decline: ticket {}/{} request from {}", self.id, self.user_addr, a.to_string());
205                    return ResultCode::TicketExpired;
206                }
207            } else {
208                return ResultCode::TicketExpired;
209            }
210        }
211
212        if self.result != ResultCode::Ok {
213            return self.result;
214        }
215
216        if Utc::now().timestamp() > self.end_time {
217            return ResultCode::TicketExpired;
218        }
219
220        if self.user_uri.is_empty() {
221            return ResultCode::NotReady;
222        }
223
224        ResultCode::Ok
225    }
226}