1use async_trait::async_trait;
2use kanidm_unix_common::unix_proto::{
3 DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse,
4};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::BTreeMap;
8use std::fmt;
9use std::time::SystemTime;
10use tokio::sync::broadcast;
11use uuid::Uuid;
1213pub type XKeyId = String;
1415pub use kanidm_hsm_crypto as tpm;
1617/// Errors that the IdProvider may return. These drive the resolver state machine
18/// and should be carefully selected to match your expected errors.
19#[derive(Debug)]
20pub enum IdpError {
21/// An error occurred in the underlying communication to the Idp. A timeout or
22 /// or other communication issue exists. The resolver will take this provider
23 /// offline.
24Transport,
25/// The provider is online but the provider module is not current authorised with
26 /// the idp. After returning this error the operation will be retried after a
27 /// successful authentication.
28ProviderUnauthorised,
29/// The provider made an invalid or illogical request to the idp, and a result
30 /// is not able to be provided to the resolver.
31BadRequest,
32/// The idp has indicated that the requested resource does not exist and should
33 /// be considered deleted, removed, or not present.
34NotFound,
35/// The idp was unable to perform an operation on the underlying hsm keystorage
36KeyStore,
37/// The idp failed to interact with the configured TPM
38Tpm,
39}
4041pub enum UserTokenState {
42/// Indicate to the resolver that the cached UserToken should be used, if present.
43UseCached,
44/// The requested entity is not found, or has been removed.
45NotFound,
4647/// Update the cache state with the data found in this UserToken.
48Update(UserToken),
49}
5051pub enum GroupTokenState {
52/// Indicate to the resolver that the cached GroupToken should be used, if present.
53UseCached,
54/// The requested entity is not found, or has been removed.
55NotFound,
5657/// Update the cache state with the data found in this GroupToken.
58Update(GroupToken),
59}
6061#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub enum Id {
63 Name(String),
64 Gid(u32),
65}
6667#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq, Hash)]
68pub enum ProviderOrigin {
69// To allow transition, we have an ignored type that effectively
70 // causes these items to be nixed.
71#[default]
72Ignore,
73/// Provided by /etc/passwd or /etc/group
74System,
75 Kanidm,
76}
7778impl fmt::Display for ProviderOrigin {
79fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80match self {
81 ProviderOrigin::Ignore => {
82write!(f, "Ignored")
83 }
84 ProviderOrigin::System => {
85write!(f, "System")
86 }
87 ProviderOrigin::Kanidm => {
88write!(f, "Kanidm")
89 }
90 }
91 }
92}
9394#[derive(Debug, Serialize, Deserialize, Clone)]
95pub struct GroupToken {
96#[serde(default)]
97pub provider: ProviderOrigin,
98pub name: String,
99pub spn: String,
100pub uuid: Uuid,
101pub gidnumber: u32,
102103#[serde(flatten)]
104pub extra_keys: BTreeMap<XKeyId, Value>,
105}
106107#[derive(Debug, Serialize, Deserialize, Clone)]
108pub struct UserToken {
109#[serde(default)]
110pub provider: ProviderOrigin,
111112pub name: String,
113pub spn: String,
114pub uuid: Uuid,
115pub gidnumber: u32,
116pub displayname: String,
117pub shell: Option<String>,
118pub groups: Vec<GroupToken>,
119120// Could there be a better type here?
121pub sshkeys: Vec<String>,
122// Defaults to false.
123pub valid: bool,
124125// These are opaque extra keys that the provider can interpret for internal
126 // functions.
127#[serde(flatten)]
128pub extra_keys: BTreeMap<XKeyId, Value>,
129}
130131#[derive(Debug)]
132pub enum AuthCredHandler {
133 Password,
134 DeviceAuthorizationGrant,
135/// Additional data required by the provider to complete the
136 /// authentication, but not required by PAM
137 ///
138 /// Sadly due to how this is passed around we can't make this a
139 /// generic associated type, else it would have to leak up to the
140 /// daemon.
141 ///
142 /// ⚠️ TODO: Optimally this should actually be a tokio oneshot receiver
143 /// with the decision from a task that is spawned.
144MFA {
145 data: Vec<String>,
146 },
147 SetupPin,
148 Pin,
149}
150151pub enum AuthRequest {
152 Password,
153 DeviceAuthorizationGrant {
154 data: DeviceAuthorizationResponse,
155 },
156 MFACode {
157 msg: String,
158 },
159 MFAPoll {
160/// Message to display to the user.
161msg: String,
162/// Interval in seconds between poll attempts.
163polling_interval: u32,
164 },
165 MFAPollWait,
166 SetupPin {
167/// Message to display to the user.
168msg: String,
169 },
170 Pin,
171}
172173#[allow(clippy::from_over_into)]
174impl Into<PamAuthResponse> for AuthRequest {
175fn into(self) -> PamAuthResponse {
176match self {
177 AuthRequest::Password => PamAuthResponse::Password,
178 AuthRequest::DeviceAuthorizationGrant { data } => {
179 PamAuthResponse::DeviceAuthorizationGrant { data }
180 }
181 AuthRequest::MFACode { msg } => PamAuthResponse::MFACode { msg },
182 AuthRequest::MFAPoll {
183 msg,
184 polling_interval,
185 } => PamAuthResponse::MFAPoll {
186 msg,
187 polling_interval,
188 },
189 AuthRequest::MFAPollWait => PamAuthResponse::MFAPollWait,
190 AuthRequest::SetupPin { msg } => PamAuthResponse::SetupPin { msg },
191 AuthRequest::Pin => PamAuthResponse::Pin,
192 }
193 }
194}
195196pub enum AuthResult {
197 Success,
198 SuccessUpdate { new_token: UserToken },
199 Denied,
200 Next(AuthRequest),
201}
202203#[async_trait]
204#[allow(clippy::too_many_arguments)]
205pub trait IdProvider {
206/// Retrieve this providers origin
207fn origin(&self) -> ProviderOrigin;
208209/// Attempt to go online *immediately*
210async fn attempt_online(&self, _tpm: &mut tpm::BoxedDynTpm, _now: SystemTime) -> bool;
211212/// Mark that this provider should attempt to go online next time it
213 /// receives a request
214async fn mark_next_check(&self, _now: SystemTime);
215216/// Force this provider offline immediately.
217async fn mark_offline(&self);
218219/// Determine if this provider has a configured extension of a local system group
220 /// with remote members.
221fn has_map_group(&self, local: &str) -> Option<&Id>;
222223// This is similar to a "domain join" process. What do we actually need to pass here
224 // for this to work for kanidm or himmelblau? Should we make it take a generic?
225 /*
226 async fn configure_machine_identity(
227 &self,
228 _keystore: &mut KeyStoreTxn,
229 _tpm: &mut tpm::BoxedDynTpm,
230 _machine_key: &tpm::MachineKey,
231 ) -> Result<(), IdpError> {
232 Ok(())
233 }
234 */
235236async fn unix_user_get(
237&self,
238 _id: &Id,
239 _token: Option<&UserToken>,
240 _tpm: &mut tpm::BoxedDynTpm,
241 _now: SystemTime,
242 ) -> Result<UserTokenState, IdpError>;
243244async fn unix_user_online_auth_init(
245&self,
246 _account_id: &str,
247 _token: &UserToken,
248 _tpm: &mut tpm::BoxedDynTpm,
249 _shutdown_rx: &broadcast::Receiver<()>,
250 ) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
251252async fn unix_user_online_auth_step(
253&self,
254 _account_id: &str,
255 _current_token: Option<&UserToken>,
256 _cred_handler: &mut AuthCredHandler,
257 _pam_next_req: PamAuthRequest,
258 _tpm: &mut tpm::BoxedDynTpm,
259 _shutdown_rx: &broadcast::Receiver<()>,
260 ) -> Result<AuthResult, IdpError>;
261262async fn unix_unknown_user_online_auth_init(
263&self,
264 _account_id: &str,
265 _tpm: &mut tpm::BoxedDynTpm,
266 _shutdown_rx: &broadcast::Receiver<()>,
267 ) -> Result<Option<(AuthRequest, AuthCredHandler)>, IdpError>;
268269async fn unix_user_offline_auth_init(
270&self,
271 _token: &UserToken,
272 ) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
273274// I thought about this part of the interface a lot. we could have the
275 // provider actually need to check the password or credentials, but then
276 // we need to rework the tpm/crypto engine to be an argument to pass here
277 // as well the cached credentials.
278 //
279 // As well, since this is "offline auth" the provider isn't really "doing"
280 // anything special here - when you say you want offline password auth, the
281 // resolver can just do it for you for all the possible implementations.
282 // This is similar for offline ctap2 as well, or even offline totp.
283 //
284 // I think in the future we could reconsider this and let the provider be
285 // involved if there is some "custom logic" or similar that is needed but
286 // for now I think making it generic is a good first step and we can change
287 // it later.
288 //
289 // EDIT 04042024: When we're performing an offline PIN auth, the PIN can
290 // unlock the associated TPM key. While we can't perform a full request
291 // for an auth token, we can verify that the PIN successfully unlocks the
292 // TPM key.
293async fn unix_user_offline_auth_step(
294&self,
295 _current_token: Option<&UserToken>,
296 _session_token: &UserToken,
297 _cred_handler: &mut AuthCredHandler,
298 _pam_next_req: PamAuthRequest,
299 _tpm: &mut tpm::BoxedDynTpm,
300 ) -> Result<AuthResult, IdpError>;
301302async fn unix_user_authorise(&self, _token: &UserToken) -> Result<Option<bool>, IdpError>;
303304async fn unix_group_get(
305&self,
306 id: &Id,
307 _tpm: &mut tpm::BoxedDynTpm,
308 _now: SystemTime,
309 ) -> Result<GroupTokenState, IdpError>;
310}