kanidmd_lib/server/
identity.rs

1//! Contains structures related to the Identity that initiated an `Event` in the
2//! server. Generally this Identity is what will have access controls applied to
3//! and this provides the set of `Limits` to confine how many resources that the
4//! identity may consume during operations to prevent denial-of-service.
5
6use crate::be::Limits;
7use std::collections::BTreeSet;
8use std::hash::Hash;
9use std::net::IpAddr;
10use std::sync::Arc;
11use uuid::uuid;
12
13use kanidm_proto::internal::{ApiTokenPurpose, UatPurpose};
14
15use serde::{Deserialize, Serialize};
16
17use crate::prelude::*;
18use crate::value::Session;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum Source {
22    Internal,
23    Https(IpAddr),
24    Ldaps(IpAddr),
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum AccessScope {
29    ReadOnly,
30    ReadWrite,
31    Synchronise,
32}
33
34impl std::fmt::Display for AccessScope {
35    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
36        match self {
37            AccessScope::ReadOnly => write!(f, "read only"),
38            AccessScope::ReadWrite => write!(f, "read write"),
39            AccessScope::Synchronise => write!(f, "synchronise"),
40        }
41    }
42}
43
44impl From<&ApiTokenPurpose> for AccessScope {
45    fn from(purpose: &ApiTokenPurpose) -> Self {
46        match purpose {
47            ApiTokenPurpose::ReadOnly => AccessScope::ReadOnly,
48            ApiTokenPurpose::ReadWrite => AccessScope::ReadWrite,
49            ApiTokenPurpose::Synchronise => AccessScope::Synchronise,
50        }
51    }
52}
53
54impl From<&UatPurpose> for AccessScope {
55    fn from(purpose: &UatPurpose) -> Self {
56        match purpose {
57            UatPurpose::ReadOnly => AccessScope::ReadOnly,
58            UatPurpose::ReadWrite { .. } => AccessScope::ReadWrite,
59        }
60    }
61}
62
63#[derive(Debug, Clone)]
64/// Metadata and the entry of the current Identity which is an external account/user.
65pub struct IdentUser {
66    pub entry: Arc<EntrySealedCommitted>,
67    // IpAddr?
68    // Other metadata?
69}
70
71#[derive(Debug, Clone)]
72/// The type of Identity that is related to this session.
73pub enum IdentType {
74    User(IdentUser),
75    Synch(Uuid),
76    Internal,
77}
78
79#[derive(Debug, Clone, PartialEq, Hash, Ord, PartialOrd, Eq, Serialize, Deserialize)]
80/// A unique identifier of this Identity, that can be associated to various
81/// caching components.
82pub enum IdentityId {
83    // Time stamp of the originating event.
84    // The uuid of the originating user
85    User(Uuid),
86    Synch(Uuid),
87    Internal,
88}
89
90impl From<&IdentityId> for Uuid {
91    fn from(ident: &IdentityId) -> Uuid {
92        match ident {
93            IdentityId::User(uuid) | IdentityId::Synch(uuid) => *uuid,
94            IdentityId::Internal => UUID_SYSTEM,
95        }
96    }
97}
98
99impl From<&IdentType> for IdentityId {
100    fn from(idt: &IdentType) -> Self {
101        match idt {
102            IdentType::Internal => IdentityId::Internal,
103            IdentType::User(u) => IdentityId::User(u.entry.get_uuid()),
104            IdentType::Synch(u) => IdentityId::Synch(*u),
105        }
106    }
107}
108
109#[derive(Debug, Clone)]
110/// An identity that initiated an `Event`. Contains extra details about the session
111/// and other info that can assist with server decision making.
112pub struct Identity {
113    pub origin: IdentType,
114    #[allow(dead_code)]
115    source: Source,
116    pub(crate) session_id: Uuid,
117    pub(crate) scope: AccessScope,
118    limits: Limits,
119}
120
121impl std::fmt::Display for Identity {
122    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123        match &self.origin {
124            IdentType::Internal => write!(f, "Internal ({})", self.scope),
125            IdentType::Synch(u) => write!(f, "Synchronise ({}) ({})", u, self.scope),
126            IdentType::User(u) => {
127                let nv = u.entry.get_uuid2spn();
128                write!(
129                    f,
130                    "User( {}, {} ) ({}, {})",
131                    nv.to_proto_string_clone(),
132                    u.entry.get_uuid().as_hyphenated(),
133                    self.session_id,
134                    self.scope
135                )
136            }
137        }
138    }
139}
140
141impl Identity {
142    pub(crate) fn new(
143        origin: IdentType,
144        source: Source,
145        session_id: Uuid,
146        scope: AccessScope,
147        limits: Limits,
148    ) -> Self {
149        Self {
150            origin,
151            source,
152            session_id,
153            scope,
154            limits,
155        }
156    }
157
158    #[allow(dead_code)]
159    pub(crate) fn source(&self) -> &Source {
160        &self.source
161    }
162
163    pub(crate) fn limits(&self) -> &Limits {
164        &self.limits
165    }
166
167    #[cfg(test)]
168    pub(crate) fn limits_mut(&mut self) -> &mut Limits {
169        &mut self.limits
170    }
171
172    pub(crate) fn from_internal() -> Self {
173        Identity {
174            origin: IdentType::Internal,
175            source: Source::Internal,
176            session_id: uuid!("00000000-0000-0000-0000-000000000000"),
177            scope: AccessScope::ReadWrite,
178            limits: Limits::unlimited(),
179        }
180    }
181
182    #[cfg(test)]
183    pub(crate) fn from_impersonate_entry_readonly(
184        entry: Arc<Entry<EntrySealed, EntryCommitted>>,
185    ) -> Self {
186        Identity {
187            origin: IdentType::User(IdentUser { entry }),
188            source: Source::Internal,
189            session_id: uuid!("00000000-0000-0000-0000-000000000000"),
190            scope: AccessScope::ReadOnly,
191            limits: Limits::unlimited(),
192        }
193    }
194
195    #[cfg(test)]
196    pub fn from_impersonate_entry_readwrite(
197        entry: Arc<Entry<EntrySealed, EntryCommitted>>,
198    ) -> Self {
199        Identity {
200            origin: IdentType::User(IdentUser { entry }),
201            source: Source::Internal,
202            session_id: uuid!("00000000-0000-0000-0000-000000000000"),
203            scope: AccessScope::ReadWrite,
204            limits: Limits::unlimited(),
205        }
206    }
207
208    pub fn access_scope(&self) -> AccessScope {
209        self.scope
210    }
211
212    pub fn project_with_scope(&self, scope: AccessScope) -> Self {
213        let mut new = self.clone();
214        new.scope = scope;
215        new
216    }
217
218    pub fn get_session_id(&self) -> Uuid {
219        self.session_id
220    }
221
222    pub fn get_session(&self) -> Option<&Session> {
223        match &self.origin {
224            IdentType::Internal | IdentType::Synch(_) => None,
225            IdentType::User(u) => u
226                .entry
227                .get_ava_as_session_map(Attribute::UserAuthTokenSession)
228                .and_then(|sessions| sessions.get(&self.session_id)),
229        }
230    }
231
232    pub fn get_user_entry(&self) -> Option<Arc<EntrySealedCommitted>> {
233        match &self.origin {
234            IdentType::Internal | IdentType::Synch(_) => None,
235            IdentType::User(u) => Some(u.entry.clone()),
236        }
237    }
238
239    pub fn from_impersonate(ident: &Self) -> Self {
240        // TODO #64 ?: In the future, we could change some of this data
241        // to reflect the fact we are in fact impersonating the action
242        // rather than the user explicitly requesting it. Could matter
243        // to audits and logs to determine what happened.
244        ident.clone()
245    }
246
247    pub fn is_internal(&self) -> bool {
248        matches!(self.origin, IdentType::Internal)
249    }
250
251    pub fn get_uuid(&self) -> Option<Uuid> {
252        match &self.origin {
253            IdentType::Internal => None,
254            IdentType::User(u) => Some(u.entry.get_uuid()),
255            IdentType::Synch(u) => Some(*u),
256        }
257    }
258
259    /// Indicate if the session associated with this identity has a session
260    /// that can logout. Examples of sessions that *can not* logout are anonymous,
261    /// tokens, or PIV sessions.
262    pub fn can_logout(&self) -> bool {
263        match &self.origin {
264            IdentType::Internal => false,
265            IdentType::User(u) => u.entry.get_uuid() != UUID_ANONYMOUS,
266            IdentType::Synch(_) => false,
267        }
268    }
269
270    pub fn get_event_origin_id(&self) -> IdentityId {
271        IdentityId::from(&self.origin)
272    }
273
274    #[cfg(test)]
275    pub fn has_claim(&self, claim: &str) -> bool {
276        match &self.origin {
277            IdentType::Internal | IdentType::Synch(_) => false,
278            IdentType::User(u) => u
279                .entry
280                .attribute_equality(Attribute::Claim, &PartialValue::new_iutf8(claim)),
281        }
282    }
283
284    pub fn is_memberof(&self, group: Uuid) -> bool {
285        match &self.origin {
286            IdentType::Internal | IdentType::Synch(_) => false,
287            IdentType::User(u) => u
288                .entry
289                .attribute_equality(Attribute::MemberOf, &PartialValue::Refer(group)),
290        }
291    }
292
293    pub fn get_memberof(&self) -> Option<&BTreeSet<Uuid>> {
294        match &self.origin {
295            IdentType::Internal | IdentType::Synch(_) => None,
296            IdentType::User(u) => u.entry.get_ava_refer(Attribute::MemberOf),
297        }
298    }
299
300    pub fn get_oauth2_consent_scopes(&self, oauth2_rs: Uuid) -> Option<&BTreeSet<String>> {
301        match &self.origin {
302            IdentType::Internal | IdentType::Synch(_) => None,
303            IdentType::User(u) => u
304                .entry
305                .get_ava_as_oauthscopemaps(Attribute::OAuth2ConsentScopeMap)
306                .and_then(|scope_map| scope_map.get(&oauth2_rs)),
307        }
308    }
309}