kanidmd_lib/idm/
radius.rs

1use std::time::Duration;
2
3use kanidm_proto::internal::RadiusAuthToken;
4use time::OffsetDateTime;
5use uuid::Uuid;
6
7use crate::entry::{Entry, EntryCommitted, EntryReduced};
8use crate::idm::group::Group;
9use crate::prelude::*;
10
11#[derive(Debug, Clone)]
12pub(crate) struct RadiusAccount {
13    pub name: String,
14    pub displayname: String,
15    pub uuid: Uuid,
16    pub groups: Vec<Group<()>>,
17    pub radius_secret: String,
18    pub valid_from: Option<OffsetDateTime>,
19    pub expire: Option<OffsetDateTime>,
20}
21
22impl RadiusAccount {
23    pub(crate) fn try_from_entry_reduced(
24        value: &Entry<EntryReduced, EntryCommitted>,
25        qs: &mut QueryServerReadTransaction,
26    ) -> Result<Self, OperationError> {
27        if !value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) {
28            return Err(OperationError::MissingClass(ENTRYCLASS_ACCOUNT.into()));
29        }
30
31        let radius_secret = value
32            .get_ava_single_secret(Attribute::RadiusSecret)
33            .ok_or_else(|| OperationError::MissingAttribute(Attribute::RadiusSecret))?
34            .to_string();
35
36        let name = value
37            .get_ava_single_iname(Attribute::Name)
38            .map(|s| s.to_string())
39            .ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
40
41        let uuid = value.get_uuid();
42
43        let displayname = value
44            .get_ava_single_utf8(Attribute::DisplayName)
45            .map(|s| s.to_string())
46            .ok_or_else(|| OperationError::MissingAttribute(Attribute::DisplayName))?;
47
48        let groups = Group::<()>::try_from_account_reduced(value, qs)?;
49
50        let valid_from = value.get_ava_single_datetime(Attribute::AccountValidFrom);
51
52        let expire = value.get_ava_single_datetime(Attribute::AccountExpire);
53
54        Ok(RadiusAccount {
55            name,
56            displayname,
57            uuid,
58            groups,
59            radius_secret,
60            valid_from,
61            expire,
62        })
63    }
64
65    fn is_within_valid_time(&self, ct: Duration) -> bool {
66        let cot = OffsetDateTime::UNIX_EPOCH + ct;
67
68        let vmin = if let Some(vft) = &self.valid_from {
69            // If current time greater than start time window
70            vft < &cot
71        } else {
72            // We have no time, not expired.
73            true
74        };
75        let vmax = if let Some(ext) = &self.expire {
76            // If exp greater than ct then expired.
77            &cot < ext
78        } else {
79            // If not present, we are not expired
80            true
81        };
82        // Mix the results
83        vmin && vmax
84    }
85
86    pub(crate) fn to_radiusauthtoken(
87        &self,
88        ct: Duration,
89    ) -> Result<RadiusAuthToken, OperationError> {
90        if !self.is_within_valid_time(ct) {
91            return Err(OperationError::InvalidAccountState(
92                "Account Expired".to_string(),
93            ));
94        }
95
96        // If we don't have access/permission, then just error instead.
97        // This includes if we don't have the secret.
98        Ok(RadiusAuthToken {
99            name: self.name.clone(),
100            displayname: self.displayname.clone(),
101            uuid: self.uuid.as_hyphenated().to_string(),
102            secret: self.radius_secret.clone(),
103            groups: self.groups.iter().map(|g| g.to_proto()).collect(),
104        })
105    }
106}