evergreen/common/
user.rs

1//! Shared, user-focused utility functions
2use crate as eg;
3use eg::editor::Editor;
4use eg::result::EgResult;
5use eg::EgValue;
6use md5;
7
8pub const PW_TYPE_MAIN: &str = "main";
9
10/// Returns result of True if the password provides matches the user's password.
11///
12/// # Arguments
13///
14/// * 'is_hashed' - Set to true if the password has already been md5-hashed.
15pub fn verify_migrated_password(
16    e: &mut Editor,
17    user_id: i64,
18    password: &str,
19    is_hashed: bool,
20) -> EgResult<bool> {
21    let mut computed: Option<String> = None;
22
23    if !is_hashed {
24        // Only compute / allocate a new String if required.
25        computed = Some(format!("{:x}", md5::compute(password)));
26    }
27
28    let pass_hash = computed.as_deref().unwrap_or(password);
29
30    let query = eg::hash! {
31        from: [
32            "actor.get_salt",
33            user_id,
34            PW_TYPE_MAIN,
35        ]
36    };
37
38    let salt_list = e.json_query(query)?;
39
40    if let Some(hash) = salt_list.first() {
41        if let Some(salt) = hash["actor.get_salt"].as_str() {
42            let combined = format!("{}{}", salt, pass_hash);
43            let digested = format!("{:x}", md5::compute(combined));
44
45            return verify_password(e, user_id, &digested, PW_TYPE_MAIN);
46        }
47    }
48
49    Ok(false)
50}
51
52/// Returns result of True if the password provided matches the user's password.
53///
54/// Passwords are tested as-is without any additional hashing.
55pub fn verify_password(
56    e: &mut Editor,
57    user_id: i64,
58    password: &str,
59    pw_type: &str,
60) -> EgResult<bool> {
61    let query = eg::hash! {
62        from: [
63            "actor.verify_passwd",
64            user_id,
65            pw_type,
66            password
67        ]
68    };
69
70    let verify = e.json_query(query)?;
71
72    if let Some(resp) = verify.first() {
73        Ok(resp["actor.verify_passwd"].boolish())
74    } else {
75        Err("actor.verify_passwd failed to return a response"
76            .to_string()
77            .into())
78    }
79}
80
81/// Returns a list of all org unit IDs where the provided user has
82/// the provided work permission.
83pub fn has_work_perm_at(e: &mut Editor, user_id: i64, perm: &str) -> EgResult<Vec<i64>> {
84    let dbfunc = "permission.usr_has_perm_at_all";
85
86    let query = eg::hash! { from: [dbfunc, user_id, perm] };
87
88    let values = e.json_query(query)?;
89
90    let mut orgs: Vec<i64> = Vec::new();
91    for value in values.iter() {
92        let org = value[dbfunc].int()?;
93        orgs.push(org);
94    }
95
96    Ok(orgs)
97}
98
99/// Returns counts of items out, overdue, etc. for a user.
100pub fn open_checkout_counts(e: &mut Editor, user_id: i64) -> EgResult<EgValue> {
101    match e.retrieve("ocirccount", user_id)? {
102        Some(mut c) => {
103            c["total_out"] = EgValue::from(c["out"].int()? + c["overdue"].int()?);
104            c.unbless();
105            Ok(c)
106        }
107        None => {
108            // There will be no response if the user has no open circs.
109            Ok(eg::hash! {
110                out: 0,
111                overdue: 0,
112                lost: 0,
113                claims_returned: 0,
114                long_overdue: 0,
115                total_count: 0,
116            })
117        }
118    }
119}
120
121/// Returns a summary of fines owed by a user
122pub fn fines_summary(e: &mut Editor, user_id: i64) -> EgResult<EgValue> {
123    let mut fines_list = e.search("mous", eg::hash! {usr: user_id})?;
124
125    if let Some(mut fines) = fines_list.pop() {
126        fines.unbless();
127        Ok(fines)
128    } else {
129        // Not all users have a fines summary row in the database.
130        Ok(eg::hash! {
131            balance_owed: 0,
132            total_owed: 0,
133            total_paid: 0,
134            usr: user_id
135        })
136    }
137}
138
139/// Returns a total/ready hold counts for a user.
140pub fn active_hold_counts(e: &mut Editor, user_id: i64) -> EgResult<EgValue> {
141    let query = eg::hash! {
142        select: {ahr: ["pickup_lib", "current_shelf_lib", "behind_desk"]},
143        from: "ahr",
144        where: {
145            usr: user_id,
146            fulfillment_time: EgValue::Null,
147            cancel_time: EgValue::Null,
148        }
149    };
150
151    let holds = e.json_query(query)?;
152    let total = holds.len();
153    let mut ready = 0;
154
155    for hold in holds.iter().filter(|h| !h["current_shelf_lib"].is_null()) {
156        let pickup_lib = hold["pickup_lib"].int()?;
157        let shelf_lib = hold["current_shelf_lib"].int()?;
158
159        // A hold is ready for pickup if its current shelf location is
160        // the pickup location.
161        if pickup_lib == shelf_lib {
162            ready += 1;
163        }
164    }
165
166    Ok(eg::hash! {total: total, ready: ready})
167}