mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
Implement DeviceAuthorizationGrant for MFA (#2079)
Himmelblau will use the DeviceAuthorizationGrant (defined in RFC8628) to perform MFA. This commit adds the bits to Kanidm to make that possible, using the new pam state machine code. Signed-off-by: David Mulder <dmulder@samba.org>
This commit is contained in:
parent
383592d921
commit
8401c3e1c8
|
@ -20,16 +20,15 @@ impl PasswdHooks for KanidmPasswd {
|
||||||
};
|
};
|
||||||
let req = ClientRequest::NssAccounts;
|
let req = ClientRequest::NssAccounts;
|
||||||
|
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(_) => {
|
||||||
Err(_) => {
|
return Response::Unavail;
|
||||||
return Response::Unavail;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
daemon_client
|
daemon_client
|
||||||
.call_and_wait(&req)
|
.call_and_wait(&req, cfg.unix_sock_timeout)
|
||||||
.map(|r| match r {
|
.map(|r| match r {
|
||||||
ClientResponse::NssAccounts(l) => l.into_iter().map(passwd_from_nssuser).collect(),
|
ClientResponse::NssAccounts(l) => l.into_iter().map(passwd_from_nssuser).collect(),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
|
@ -48,16 +47,15 @@ impl PasswdHooks for KanidmPasswd {
|
||||||
};
|
};
|
||||||
let req = ClientRequest::NssAccountByUid(uid);
|
let req = ClientRequest::NssAccountByUid(uid);
|
||||||
|
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(_) => {
|
||||||
Err(_) => {
|
return Response::Unavail;
|
||||||
return Response::Unavail;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
daemon_client
|
daemon_client
|
||||||
.call_and_wait(&req)
|
.call_and_wait(&req, cfg.unix_sock_timeout)
|
||||||
.map(|r| match r {
|
.map(|r| match r {
|
||||||
ClientResponse::NssAccount(opt) => opt
|
ClientResponse::NssAccount(opt) => opt
|
||||||
.map(passwd_from_nssuser)
|
.map(passwd_from_nssuser)
|
||||||
|
@ -77,16 +75,15 @@ impl PasswdHooks for KanidmPasswd {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let req = ClientRequest::NssAccountByName(name);
|
let req = ClientRequest::NssAccountByName(name);
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(_) => {
|
||||||
Err(_) => {
|
return Response::Unavail;
|
||||||
return Response::Unavail;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
daemon_client
|
daemon_client
|
||||||
.call_and_wait(&req)
|
.call_and_wait(&req, cfg.unix_sock_timeout)
|
||||||
.map(|r| match r {
|
.map(|r| match r {
|
||||||
ClientResponse::NssAccount(opt) => opt
|
ClientResponse::NssAccount(opt) => opt
|
||||||
.map(passwd_from_nssuser)
|
.map(passwd_from_nssuser)
|
||||||
|
@ -111,16 +108,15 @@ impl GroupHooks for KanidmGroup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let req = ClientRequest::NssGroups;
|
let req = ClientRequest::NssGroups;
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(_) => {
|
||||||
Err(_) => {
|
return Response::Unavail;
|
||||||
return Response::Unavail;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
daemon_client
|
daemon_client
|
||||||
.call_and_wait(&req)
|
.call_and_wait(&req, cfg.unix_sock_timeout)
|
||||||
.map(|r| match r {
|
.map(|r| match r {
|
||||||
ClientResponse::NssGroups(l) => l.into_iter().map(group_from_nssgroup).collect(),
|
ClientResponse::NssGroups(l) => l.into_iter().map(group_from_nssgroup).collect(),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
|
@ -138,16 +134,15 @@ impl GroupHooks for KanidmGroup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let req = ClientRequest::NssGroupByGid(gid);
|
let req = ClientRequest::NssGroupByGid(gid);
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(_) => {
|
||||||
Err(_) => {
|
return Response::Unavail;
|
||||||
return Response::Unavail;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
daemon_client
|
daemon_client
|
||||||
.call_and_wait(&req)
|
.call_and_wait(&req, cfg.unix_sock_timeout)
|
||||||
.map(|r| match r {
|
.map(|r| match r {
|
||||||
ClientResponse::NssGroup(opt) => opt
|
ClientResponse::NssGroup(opt) => opt
|
||||||
.map(group_from_nssgroup)
|
.map(group_from_nssgroup)
|
||||||
|
@ -167,16 +162,15 @@ impl GroupHooks for KanidmGroup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let req = ClientRequest::NssGroupByName(name);
|
let req = ClientRequest::NssGroupByName(name);
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(_) => {
|
||||||
Err(_) => {
|
return Response::Unavail;
|
||||||
return Response::Unavail;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
daemon_client
|
daemon_client
|
||||||
.call_and_wait(&req)
|
.call_and_wait(&req, cfg.unix_sock_timeout)
|
||||||
.map(|r| match r {
|
.map(|r| match r {
|
||||||
ClientResponse::NssGroup(opt) => opt
|
ClientResponse::NssGroup(opt) => opt
|
||||||
.map(group_from_nssgroup)
|
.map(group_from_nssgroup)
|
||||||
|
|
|
@ -135,16 +135,15 @@ impl PamHooks for PamKanidm {
|
||||||
let req = ClientRequest::PamAccountAllowed(account_id);
|
let req = ClientRequest::PamAccountAllowed(account_id);
|
||||||
// PamResultCode::PAM_IGNORE
|
// PamResultCode::PAM_IGNORE
|
||||||
|
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(e) => {
|
||||||
Err(e) => {
|
error!(err = ?e, "Error DaemonClientBlocking::new()");
|
||||||
error!(err = ?e, "Error DaemonClientBlocking::new()");
|
return PamResultCode::PAM_SERVICE_ERR;
|
||||||
return PamResultCode::PAM_SERVICE_ERR;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
match daemon_client.call_and_wait(&req) {
|
match daemon_client.call_and_wait(&req, cfg.unix_sock_timeout) {
|
||||||
Ok(r) => match r {
|
Ok(r) => match r {
|
||||||
ClientResponse::PamStatus(Some(true)) => {
|
ClientResponse::PamStatus(Some(true)) => {
|
||||||
debug!("PamResultCode::PAM_SUCCESS");
|
debug!("PamResultCode::PAM_SUCCESS");
|
||||||
|
@ -203,14 +202,14 @@ impl PamHooks for PamKanidm {
|
||||||
Err(e) => return e,
|
Err(e) => return e,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut daemon_client =
|
let mut timeout = cfg.unix_sock_timeout;
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
Ok(dc) => dc,
|
Ok(dc) => dc,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(err = ?e, "Error DaemonClientBlocking::new()");
|
error!(err = ?e, "Error DaemonClientBlocking::new()");
|
||||||
return PamResultCode::PAM_SERVICE_ERR;
|
return PamResultCode::PAM_SERVICE_ERR;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Later we may need to move this to a function and call it as a oneshot for auth methods
|
// Later we may need to move this to a function and call it as a oneshot for auth methods
|
||||||
// that don't require any authtoks at all. For example, imagine a user authed and they
|
// that don't require any authtoks at all. For example, imagine a user authed and they
|
||||||
|
@ -242,7 +241,7 @@ impl PamHooks for PamKanidm {
|
||||||
let mut req = ClientRequest::PamAuthenticateInit(account_id);
|
let mut req = ClientRequest::PamAuthenticateInit(account_id);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match daemon_client.call_and_wait(&req) {
|
match daemon_client.call_and_wait(&req, timeout) {
|
||||||
Ok(r) => match r {
|
Ok(r) => match r {
|
||||||
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Success) => {
|
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Success) => {
|
||||||
return PamResultCode::PAM_SUCCESS;
|
return PamResultCode::PAM_SUCCESS;
|
||||||
|
@ -282,9 +281,34 @@ impl PamHooks for PamKanidm {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Now setup the request for the next loop.
|
// Now setup the request for the next loop.
|
||||||
|
timeout = cfg.unix_sock_timeout;
|
||||||
req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Password { cred });
|
req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Password { cred });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
ClientResponse::PamAuthenticateStepResponse(
|
||||||
|
PamAuthResponse::DeviceAuthorizationGrant { data },
|
||||||
|
) => {
|
||||||
|
let msg = match &data.message {
|
||||||
|
Some(msg) => msg.clone(),
|
||||||
|
None => format!("Using a browser on another device, visit:\n{}\nAnd enter the code:\n{}",
|
||||||
|
data.verification_uri, data.user_code)
|
||||||
|
};
|
||||||
|
match conv.send(PAM_TEXT_INFO, &msg) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
if opts.debug {
|
||||||
|
println!("Message prompt failed");
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = u64::from(data.expires_in);
|
||||||
|
req = ClientRequest::PamAuthenticateStep(
|
||||||
|
PamAuthRequest::DeviceAuthorizationGrant { data },
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// unexpected response.
|
// unexpected response.
|
||||||
error!(err = ?r, "PAM_IGNORE, unexpected resolver response");
|
error!(err = ?r, "PAM_IGNORE, unexpected resolver response");
|
||||||
|
@ -349,16 +373,15 @@ impl PamHooks for PamKanidm {
|
||||||
};
|
};
|
||||||
let req = ClientRequest::PamAccountBeginSession(account_id);
|
let req = ClientRequest::PamAccountBeginSession(account_id);
|
||||||
|
|
||||||
let mut daemon_client =
|
let mut daemon_client = match DaemonClientBlocking::new(cfg.sock_path.as_str()) {
|
||||||
match DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) {
|
Ok(dc) => dc,
|
||||||
Ok(dc) => dc,
|
Err(e) => {
|
||||||
Err(e) => {
|
error!(err = ?e, "Error DaemonClientBlocking::new()");
|
||||||
error!(err = ?e, "Error DaemonClientBlocking::new()");
|
return PamResultCode::PAM_SERVICE_ERR;
|
||||||
return PamResultCode::PAM_SERVICE_ERR;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
match daemon_client.call_and_wait(&req) {
|
match daemon_client.call_and_wait(&req, cfg.unix_sock_timeout) {
|
||||||
Ok(ClientResponse::Ok) => {
|
Ok(ClientResponse::Ok) => {
|
||||||
// println!("PAM_SUCCESS");
|
// println!("PAM_SUCCESS");
|
||||||
PamResultCode::PAM_SUCCESS
|
PamResultCode::PAM_SUCCESS
|
||||||
|
|
|
@ -6,34 +6,50 @@ use std::time::{Duration, SystemTime};
|
||||||
use crate::unix_proto::{ClientRequest, ClientResponse};
|
use crate::unix_proto::{ClientRequest, ClientResponse};
|
||||||
|
|
||||||
pub struct DaemonClientBlocking {
|
pub struct DaemonClientBlocking {
|
||||||
timeout: Duration,
|
|
||||||
stream: UnixStream,
|
stream: UnixStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DaemonClientBlocking {
|
impl DaemonClientBlocking {
|
||||||
pub fn new(path: &str, timeout: u64) -> Result<DaemonClientBlocking, Box<dyn Error>> {
|
pub fn new(path: &str) -> Result<DaemonClientBlocking, Box<dyn Error>> {
|
||||||
let timeout = Duration::from_secs(timeout);
|
debug!(%path);
|
||||||
|
|
||||||
debug!(%path, ?timeout);
|
|
||||||
|
|
||||||
let stream = UnixStream::connect(path)
|
let stream = UnixStream::connect(path)
|
||||||
.and_then(|socket| socket.set_read_timeout(Some(timeout)).map(|_| socket))
|
|
||||||
.and_then(|socket| socket.set_write_timeout(Some(timeout)).map(|_| socket))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("stream setup error -> {:?}", e);
|
error!("stream setup error -> {:?}", e);
|
||||||
e
|
e
|
||||||
})
|
})
|
||||||
.map_err(Box::new)?;
|
.map_err(Box::new)?;
|
||||||
|
|
||||||
Ok(DaemonClientBlocking { timeout, stream })
|
Ok(DaemonClientBlocking { stream })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_and_wait(&mut self, req: &ClientRequest) -> Result<ClientResponse, Box<dyn Error>> {
|
pub fn call_and_wait(
|
||||||
|
&mut self,
|
||||||
|
req: &ClientRequest,
|
||||||
|
timeout: u64,
|
||||||
|
) -> Result<ClientResponse, Box<dyn Error>> {
|
||||||
|
let timeout = Duration::from_secs(timeout);
|
||||||
|
|
||||||
let data = serde_json::to_vec(&req).map_err(|e| {
|
let data = serde_json::to_vec(&req).map_err(|e| {
|
||||||
error!("socket encoding error -> {:?}", e);
|
error!("socket encoding error -> {:?}", e);
|
||||||
Box::new(IoError::new(ErrorKind::Other, "JSON encode error"))
|
Box::new(IoError::new(ErrorKind::Other, "JSON encode error"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
match self.stream.set_read_timeout(Some(timeout)) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("stream setup error -> {:?}", e);
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match self.stream.set_write_timeout(Some(timeout)) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("stream setup error -> {:?}", e);
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.stream
|
self.stream
|
||||||
.write_all(data.as_slice())
|
.write_all(data.as_slice())
|
||||||
.and_then(|_| self.stream.flush())
|
.and_then(|_| self.stream.flush())
|
||||||
|
@ -52,7 +68,7 @@ impl DaemonClientBlocking {
|
||||||
loop {
|
loop {
|
||||||
let mut buffer = [0; 1024];
|
let mut buffer = [0; 1024];
|
||||||
let durr = SystemTime::now().duration_since(start).map_err(Box::new)?;
|
let durr = SystemTime::now().duration_since(start).map_err(Box::new)?;
|
||||||
if durr > self.timeout {
|
if durr > timeout {
|
||||||
error!("Socket timeout");
|
error!("Socket timeout");
|
||||||
// timed out, not enough activity.
|
// timed out, not enough activity.
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::unix_proto::{PamAuthRequest, PamAuthResponse};
|
use crate::unix_proto::{DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -55,10 +55,12 @@ pub struct UserToken {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AuthCredHandler {
|
pub enum AuthCredHandler {
|
||||||
Password,
|
Password,
|
||||||
|
DeviceAuthorizationGrant,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AuthRequest {
|
pub enum AuthRequest {
|
||||||
Password,
|
Password,
|
||||||
|
DeviceAuthorizationGrant { data: DeviceAuthorizationResponse },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::from_over_into)]
|
#[allow(clippy::from_over_into)]
|
||||||
|
@ -66,6 +68,9 @@ impl Into<PamAuthResponse> for AuthRequest {
|
||||||
fn into(self) -> PamAuthResponse {
|
fn into(self) -> PamAuthResponse {
|
||||||
match self {
|
match self {
|
||||||
AuthRequest::Password => PamAuthResponse::Password,
|
AuthRequest::Password => PamAuthResponse::Password,
|
||||||
|
AuthRequest::DeviceAuthorizationGrant { data } => {
|
||||||
|
PamAuthResponse::DeviceAuthorizationGrant { data }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,13 +223,18 @@ impl IdProvider for KanidmProvider {
|
||||||
Err(IdpError::BadRequest)
|
Err(IdpError::BadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // For future when we have different auth combos/types.
|
}
|
||||||
/*
|
(
|
||||||
_ => {
|
AuthCredHandler::DeviceAuthorizationGrant,
|
||||||
error!("invalid authentication request state");
|
PamAuthRequest::DeviceAuthorizationGrant { .. },
|
||||||
Err(IdpError::BadRequest)
|
) => {
|
||||||
}
|
error!("DeviceAuthorizationGrant not implemented!");
|
||||||
*/
|
Err(IdpError::BadRequest)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("invalid authentication request state");
|
||||||
|
Err(IdpError::BadRequest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1047,6 +1047,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(AuthCredHandler::Password, _) => {
|
||||||
|
// AuthCredHandler::Password is only valid with a cred provided
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
(AuthCredHandler::DeviceAuthorizationGrant, _) => {
|
||||||
|
// AuthCredHandler::DeviceAuthorizationGrant is invalid for offline auth
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1116,6 +1124,10 @@ where
|
||||||
// Can continue!
|
// Can continue!
|
||||||
auth_session
|
auth_session
|
||||||
}
|
}
|
||||||
|
(auth_session, PamAuthResponse::DeviceAuthorizationGrant { .. }) => {
|
||||||
|
// Can continue!
|
||||||
|
auth_session
|
||||||
|
}
|
||||||
(_, PamAuthResponse::Unknown) => return Ok(None),
|
(_, PamAuthResponse::Unknown) => return Ok(None),
|
||||||
(_, PamAuthResponse::Denied) => return Ok(Some(false)),
|
(_, PamAuthResponse::Denied) => return Ok(Some(false)),
|
||||||
(_, PamAuthResponse::Success) => {
|
(_, PamAuthResponse::Success) => {
|
||||||
|
|
|
@ -16,12 +16,27 @@ pub struct NssGroup {
|
||||||
pub members: Vec<String>,
|
pub members: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RFC8628: 3.2. Device Authorization Response */
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct DeviceAuthorizationResponse {
|
||||||
|
pub device_code: String,
|
||||||
|
pub user_code: String,
|
||||||
|
pub verification_uri: String,
|
||||||
|
pub verification_uri_complete: Option<String>,
|
||||||
|
pub expires_in: u32,
|
||||||
|
pub interval: Option<u32>,
|
||||||
|
/* The message is not part of RFC8628, but an add-on from MS. Listed
|
||||||
|
* optional here to support all implementations. */
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum PamAuthResponse {
|
pub enum PamAuthResponse {
|
||||||
Unknown,
|
Unknown,
|
||||||
Success,
|
Success,
|
||||||
Denied,
|
Denied,
|
||||||
Password,
|
Password,
|
||||||
|
DeviceAuthorizationGrant { data: DeviceAuthorizationResponse },
|
||||||
/*
|
/*
|
||||||
MFACode {
|
MFACode {
|
||||||
},
|
},
|
||||||
|
@ -32,11 +47,11 @@ pub enum PamAuthResponse {
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum PamAuthRequest {
|
pub enum PamAuthRequest {
|
||||||
Password { cred: String },
|
Password { cred: String },
|
||||||
/*
|
DeviceAuthorizationGrant { data: DeviceAuthorizationResponse }, /*
|
||||||
MFACode {
|
MFACode {
|
||||||
cred: Option<PamCred>
|
cred: Option<PamCred>
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
Loading…
Reference in a new issue