show client username, check whether credential exists before requesting confirmation from frontend

This commit is contained in:
Joseph Montanaro 2024-11-25 11:22:27 -05:00
parent 9bc9cb56c1
commit c6e22fc91b
8 changed files with 52 additions and 14 deletions

View File

@ -5,7 +5,8 @@ use sysinfo::{
SystemExt,
Pid,
PidExt,
ProcessExt
ProcessExt,
UserExt,
};
use serde::{Serialize, Deserialize};
@ -16,13 +17,16 @@ use crate::errors::*;
pub struct Client {
pub pid: u32,
pub exe: Option<PathBuf>,
pub username: Option<String>,
}
pub fn get_client(pid: u32, parent: bool) -> Result<Client, ClientInfoError> {
let sys_pid = Pid::from_u32(pid);
let mut sys = System::new();
let mut sys = System::new();
sys.refresh_process(sys_pid);
sys.refresh_users_list();
let mut proc = sys.process(sys_pid)
.ok_or(ClientInfoError::ProcessNotFound)?;
@ -34,10 +38,15 @@ pub fn get_client(pid: u32, parent: bool) -> Result<Client, ClientInfoError> {
.ok_or(ClientInfoError::ParentProcessNotFound)?;
}
let username = proc.user_id()
.map(|uid| sys.get_user_by_id(uid))
.flatten()
.map(|u| u.name().to_owned());
let exe = match proc.exe() {
p if p == Path::new("") => None,
p => Some(PathBuf::from(p)),
};
Ok(Client { pid: proc.pid().as_u32(), exe })
Ok(Client { pid: proc.pid().as_u32(), exe, username })
}

View File

@ -139,3 +139,10 @@ pub trait PersistentCredential: for<'a> Deserialize<'a> + Sized {
Ok(creds)
}
}
pub fn random_uuid() -> Uuid {
// a bit weird to use salt() for this, but it's convenient
let random_bytes = Crypto::salt();
Uuid::from_slice(&random_bytes[..16]).unwrap()
}

View File

@ -6,12 +6,11 @@ use ssh_agent_lib::proto::message::{
};
use tauri::{AppHandle, Manager};
use tokio_stream::StreamExt;
use tokio::sync::oneshot;
use tokio_util::codec::Framed;
use crate::clientinfo;
use crate::errors::*;
use crate::ipc::{Approval, RequestNotification, RequestNotificationDetail};
use crate::ipc::{Approval, RequestNotificationDetail};
use crate::state::AppState;
use super::{CloseWaiter, Stream};

View File

@ -1,17 +1,17 @@
use sqlx::types::uuid::Uuid;
use tauri::{AppHandle, Manager};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::oneshot;
use crate::clientinfo::{self, Client};
use crate::credentials::{
self,
Credential,
CredentialRecord,
Crypto,
DockerCredential
DockerCredential,
};
use crate::errors::*;
use crate::ipc::{Approval, AwsRequestNotification, RequestNotificationDetail, RequestResponse};
use crate::ipc::{Approval, RequestNotificationDetail};
use crate::shortcuts::{self, ShortcutAction};
use crate::state::AppState;
use super::{
@ -116,11 +116,22 @@ async fn get_docker_credential(
app_handle: AppHandle,
waiter: CloseWaiter<'_>,
) -> Result<CliResponse, HandlerError> {
let state = app_handle.state::<AppState>();
let credential_id = state.credential_id(&server_url).await.unwrap_or(None);
if credential_id.is_none() {
return Err(
HandlerError::NoCredentials(
GetCredentialsError::Load(
LoadCredentialsError::NoCredentials
)
)
);
}
let detail = RequestNotificationDetail::new_docker(client, server_url.clone());
let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
match response.approval {
Approval::Approved => {
let state = app_handle.state::<AppState>();
let creds = state.get_docker_credential(&server_url).await?;
Ok(CliResponse::Credential(CliCredential::Docker(creds)))
},
@ -139,9 +150,12 @@ async fn store_docker_credential(
// eventually ask the frontend to confirm here
// a bit weird but convenient
let random_bytes = Crypto::salt();
let id = Uuid::from_slice(&random_bytes[..16]).unwrap();
// for some reason Docker likes to call `store` immediately with whatever it gets
// back from every `get` operation, so we have to check for an existing credential
let id = state.credential_id(&docker_credential.server_url)
.await
.map_err(|e| GetCredentialsError::Load(e))?
.unwrap_or_else(|| credentials::random_uuid());
let record = CredentialRecord {
id,

View File

@ -9,7 +9,6 @@ use tokio::io::AsyncReadExt;
use tokio::sync::oneshot;
use serde::{Serialize, Deserialize};
use crate::clientinfo::Client;
use crate::credentials::{
AwsBaseCredential,
AwsSessionCredential,

View File

@ -148,6 +148,13 @@ impl AppState {
}
}
pub async fn credential_id(&self, name: &str) -> Result<Option<Uuid>, LoadCredentialsError> {
let res = sqlx::query_scalar!(r#"SELECT id as "id: Uuid" FROM credentials WHERE name = ?"#, name)
.fetch_optional(&self.pool)
.await;
Ok(res?)
}
pub async fn save_credential(&self, record: CredentialRecord) -> Result<(), SaveCredentialsError> {
let session = self.app_session.read().await;
let crypto = session.try_get_crypto()?;

View File

@ -7,6 +7,7 @@
import ShowResponse from './approve/ShowResponse.svelte';
import Unlock from './Unlock.svelte';
console.log($appState.currentRequest);
// Extra 50ms so the window can finish disappearing before the redraw
const rehideDelay = Math.min(5000, $appState.config.rehide_ms + 100);

View File

@ -14,7 +14,7 @@
// Extract executable name from full path
const client = $appState.currentRequest.client;
const m = client.exe?.match(/\/([^/]+?$)|\\([^\\]+?$)/);
const appName = m[1] || m[2];
const appName = m ? m[1] || m[2] : '';
const dispatch = createEventDispatcher();
@ -61,6 +61,8 @@
<code class="">{@html client.exe ? breakPath(client.exe) : 'Unknown'}</code>
<div class="text-right">PID:</div>
<code>{client.pid}</code>
<div class="text-right">User:</div>
<code>{client.username ?? 'Unknown'}</code>
</div>
</div>