show client username, check whether credential exists before requesting confirmation from frontend
This commit is contained in:
		| @@ -5,7 +5,8 @@ use sysinfo::{ | ||||
|     SystemExt, | ||||
|     Pid, | ||||
|     PidExt, | ||||
|     ProcessExt | ||||
|     ProcessExt, | ||||
|     UserExt, | ||||
| }; | ||||
| use serde::{Serialize, Deserialize}; | ||||
|  | ||||
| @@ -16,6 +17,7 @@ use crate::errors::*; | ||||
| pub struct Client { | ||||
|     pub pid: u32, | ||||
|     pub exe: Option<PathBuf>, | ||||
|     pub username: Option<String>, | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -23,6 +25,8 @@ pub fn get_client(pid: u32, parent: bool) -> Result<Client, ClientInfoError> { | ||||
|     let sys_pid = Pid::from_u32(pid); | ||||
|     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 }) | ||||
| } | ||||
|   | ||||
| @@ -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() | ||||
| } | ||||
|   | ||||
| @@ -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}; | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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()?; | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user