1use crate as eg;
2use eg::common::settings::Settings;
3use eg::constants as C;
4use eg::date;
5use eg::osrf::cache::Cache;
6use eg::osrf::sclient::HostSettings;
7use eg::util;
8use eg::{Client, Editor, EgError, EgEvent, EgResult, EgValue};
9use md5;
10use std::fmt;
11
12const LOGIN_TIMEOUT: u64 = 30;
13
14const DEFAULT_RESET_INTERVAL: i32 = 10 * 60;
16
17fn cache_key(token: &str) -> String {
18 format!("{}{}", C::OILS_AUTH_CACHE_PRFX, token)
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum LoginType {
23 Temp,
24 Opac,
25 Staff,
26 Persist,
27}
28
29impl TryFrom<&str> for LoginType {
30 type Error = EgError;
31 fn try_from(s: &str) -> EgResult<LoginType> {
32 match s {
33 "opac" => Ok(Self::Opac),
34 "staff" => Ok(Self::Staff),
35 "persist" => Ok(Self::Persist),
36 "temp" => Ok(Self::Temp),
37 _ => Err(format!("Invalid login type: {s}. Using temp instead").into()),
38 }
39 }
40}
41
42impl From<&LoginType> for &str {
43 fn from(lt: &LoginType) -> &'static str {
44 match *lt {
45 LoginType::Temp => "temp",
46 LoginType::Opac => "opac",
47 LoginType::Staff => "staff",
48 LoginType::Persist => "persist",
49 }
50 }
51}
52
53impl fmt::Display for LoginType {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 let s: &str = (self).into();
56 write!(f, "{}", s)
57 }
58}
59
60pub struct LoginArgs {
61 pub username: String,
62 pub password: String,
63 pub login_type: LoginType,
64 pub workstation: Option<String>,
65}
66
67impl LoginArgs {
68 pub fn new(
80 username: &str,
81 password: &str,
82 login_type: impl Into<LoginType>,
83 workstation: Option<&str>,
84 ) -> Self {
85 LoginArgs {
86 username: username.to_string(),
87 password: password.to_string(),
88 login_type: login_type.into(),
89 workstation: workstation.map(|w| w.to_string()),
90 }
91 }
92
93 pub fn username(&self) -> &str {
94 &self.username
95 }
96
97 pub fn password(&self) -> &str {
98 &self.password
99 }
100
101 pub fn login_type(&self) -> &LoginType {
102 &self.login_type
103 }
104
105 pub fn workstation(&self) -> Option<&str> {
106 self.workstation.as_deref()
107 }
108
109 pub fn to_eg_value(&self) -> EgValue {
110 let lt: &str = self.login_type().into();
111
112 let mut jv = eg::hash! {
113 username: self.username(),
114 password: self.password(),
115 "type": lt,
116 };
117
118 if let Some(w) = &self.workstation {
119 jv["workstation"] = EgValue::from(w.as_str());
120 }
121
122 jv
123 }
124}
125
126#[derive(Debug)]
127pub struct InternalLoginArgs {
128 pub user_id: i64,
129 pub org_unit: Option<i64>,
130 pub login_type: LoginType,
131 pub workstation: Option<String>,
132}
133
134impl InternalLoginArgs {
135 pub fn new(user_id: i64, login_type: impl Into<LoginType>) -> Self {
136 InternalLoginArgs {
137 user_id,
138 login_type: login_type.into(),
139 org_unit: None,
140 workstation: None,
141 }
142 }
143 pub fn set_org_unit(&mut self, org_unit: i64) {
144 self.org_unit = Some(org_unit);
145 }
146 pub fn set_workstation(&mut self, workstation: &str) {
147 self.workstation = Some(workstation.to_string());
148 }
149
150 pub fn to_eg_value(&self) -> EgValue {
151 let lt: &str = (&self.login_type).into();
152
153 let mut jv = eg::hash! {
154 "login_type": lt,
155 "user_id": self.user_id,
156 };
157
158 if let Some(w) = &self.workstation {
159 jv["workstation"] = EgValue::from(w.as_str());
160 }
161
162 if let Some(w) = self.org_unit {
163 jv["org_unit"] = EgValue::from(w);
164 }
165
166 jv
167 }
168}
169
170pub struct Session {
171 user: EgValue,
172
173 token: String,
174
175 authtime: u32,
177
178 workstation: Option<String>,
182
183 endtime: Option<i64>,
186
187 reset_interval: Option<i64>,
189}
190
191impl Session {
192 pub fn from_cache(token: &str) -> EgResult<Option<Session>> {
196 let mut cache_val = match Cache::get_global(&cache_key(token))? {
197 Some(v) => v,
198 None => return Ok(None),
199 };
200
201 let authtime = cache_val["authtime"].int()? as u32;
202 let user = cache_val["userobj"].take();
203 let endtime = cache_val["endtime"].as_i64();
204 let reset_interval = cache_val["reset_interval"].as_i64();
205
206 let ses = Session {
207 user,
208 authtime,
209 endtime,
210 reset_interval,
211 workstation: None,
212 token: token.to_string(),
213 };
214
215 Ok(Some(ses))
216 }
217
218 pub fn remove(&self) -> EgResult<()> {
219 Cache::del_global(&cache_key(self.token()))
220 }
221
222 pub fn logout(client: &Client, token: &str) -> EgResult<()> {
224 let mut ses = client.session("open-ils.auth");
225 let mut req = ses.request("open-ils.auth.session.delete", token)?;
226 req.recv_with_timeout(LOGIN_TIMEOUT)?;
229 Ok(())
230 }
231
232 pub fn login(client: &Client, args: &LoginArgs) -> EgResult<Option<Session>> {
236 let params = vec![args.to_eg_value()];
237 let mut ses = client.session("open-ils.auth");
238 let mut req = ses.request("open-ils.auth.login", params)?;
239
240 let eg_val = match req.recv_with_timeout(LOGIN_TIMEOUT)? {
241 Some(v) => v,
242 None => return Err("Login Timed Out".into()),
243 };
244
245 Session::handle_auth_response(&args.workstation, &eg_val)
246 }
247
248 pub fn internal_session_api(
252 client: &Client,
253 args: &InternalLoginArgs,
254 ) -> EgResult<Option<Session>> {
255 let params = vec![args.to_eg_value()];
256 let mut ses = client.session("open-ils.auth_internal");
257 let mut req = ses.request("open-ils.auth_internal.session.create", params)?;
258
259 let eg_val = match req.recv_with_timeout(LOGIN_TIMEOUT)? {
260 Some(v) => v,
261 None => return Err("Login Timed Out".into()),
262 };
263
264 Session::handle_auth_response(&args.workstation, &eg_val)
265 }
266
267 fn handle_auth_response(
268 workstation: &Option<String>,
269 response: &EgValue,
270 ) -> EgResult<Option<Session>> {
271 let mut evt = match EgEvent::parse(response) {
272 Some(e) => e,
273 None => return Err(format!("Unexpected response: {:?}", response).into()),
274 };
275
276 if !evt.is_success() {
277 log::warn!("Login failed: {evt:?}");
278 return Ok(None);
279 }
280
281 if !evt.payload().is_object() {
282 return Err(format!("Unexpected response: {}", evt).into());
283 }
284
285 let token = evt.payload_mut()["authtoken"]
286 .take_string()
287 .ok_or_else(|| "Auth cache value has invalid authtoken".to_string())?;
288
289 let authtime = evt.payload()["authtime"].int()? as u32;
290 let user = evt.payload_mut()["userobj"].take();
291
292 let mut auth_ses = Session {
293 user,
294 token,
295 authtime,
296 workstation: None,
297 endtime: None,
298 reset_interval: None,
299 };
300
301 if let Some(w) = workstation {
302 auth_ses.workstation = Some(String::from(w));
303 }
304
305 Ok(Some(auth_ses))
306 }
307
308 pub fn internal_session(editor: &mut Editor, args: &InternalLoginArgs) -> EgResult<Session> {
310 let mut user = editor
311 .retrieve("au", args.user_id)?
312 .ok_or_else(|| editor.die_event())?;
313
314 user["passwd"].take();
316
317 if let Some(workstation) = args.workstation.as_deref() {
318 let mut ws = editor
319 .search("aws", eg::hash! {"name": workstation})?
320 .pop()
321 .ok_or_else(|| editor.die_event())?;
322
323 user["wsid"] = ws["id"].take();
324 user["ws_ou"] = ws["owning_lib"].take();
325 } else {
326 user["ws_ou"] = user["home_ou"].clone();
327 }
328
329 let org_id = match args.org_unit {
330 Some(id) => id,
331 None => user["ws_ou"].int()?,
332 };
333
334 let duration = get_auth_duration(editor, org_id, user["home_ou"].int()?, &args.login_type)?;
335
336 let authtoken = format!("{:x}", md5::compute(util::random_number(20)));
337
338 let mut cache_val = eg::hash! {
339 "authtime": duration,
340 "userobj": user.clone(),
341 };
342
343 if args.login_type == LoginType::Persist {
344 cache_val["endtime"] = EgValue::from(date::epoch_secs().floor() as u32 + duration);
348
349 cache_val["reset_interval"] = DEFAULT_RESET_INTERVAL.into();
352 }
353
354 let endtime = cache_val["endtime"].as_int();
355 let reset_interval = cache_val["reset_interval"].as_int();
356
357 Cache::set_global_for(&cache_key(&authtoken), cache_val, duration)?;
358
359 let auth_ses = Session {
360 user,
361 token: authtoken,
362 authtime: duration,
363 endtime,
364 reset_interval,
365 workstation: args.workstation.clone(),
366 };
367
368 Ok(auth_ses)
369 }
370
371 pub fn token(&self) -> &str {
372 &self.token
373 }
374
375 pub fn authtime(&self) -> u32 {
376 self.authtime
377 }
378
379 pub fn workstation(&self) -> Option<&str> {
380 self.workstation.as_deref()
381 }
382
383 pub fn endtime(&self) -> Option<i64> {
384 self.endtime
385 }
386
387 pub fn reset_interval(&self) -> Option<i64> {
388 self.reset_interval
389 }
390 pub fn user(&self) -> &EgValue {
391 &self.user
392 }
393}
394
395pub fn get_auth_duration(
398 editor: &mut Editor,
399 org_id: i64,
400 user_home_ou: i64,
401 auth_type: &LoginType,
402) -> EgResult<u32> {
403 let setting_name = match auth_type {
406 LoginType::Opac => "auth.opac_timeout",
407 LoginType::Staff => "auth.staff_timeout",
408 LoginType::Temp => "auth.temp_timeout",
409 LoginType::Persist => "auth.persistent_login_interval",
410 };
411
412 let mut settings = Settings::new(editor);
413 settings.set_org_id(org_id);
414
415 let mut interval = settings.get_value(setting_name)?;
416
417 if interval.is_null() && user_home_ou != org_id {
418 settings.set_org_id(user_home_ou);
421 interval = settings.get_value(setting_name)?;
422 }
423
424 let interval_binding;
425 if interval.is_null() {
426 let setkey = format!("apps/open-ils.auth_internal/app_settings/default_timeout/{auth_type}");
430
431 interval_binding = HostSettings::get(&setkey)?.clone();
432 interval = &interval_binding;
433 }
434
435 if let Some(num) = interval.as_int() {
436 Ok(num as u32)
437 } else if let Some(s) = interval.as_str() {
438 date::interval_to_seconds(s).map(|n| n as u32)
439 } else {
440 Ok(0)
441 }
442}