224 lines
7.1 KiB
Rust
224 lines
7.1 KiB
Rust
use tauri::{AppHandle, Manager};
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
|
|
use crate::clientinfo::{self, Client};
|
|
use crate::credentials::{
|
|
self,
|
|
Credential,
|
|
CredentialRecord,
|
|
DockerCredential,
|
|
};
|
|
use crate::errors::*;
|
|
use crate::ipc::{
|
|
Approval,
|
|
RequestAction,
|
|
RequestNotificationDetail
|
|
};
|
|
use crate::shortcuts::{self, ShortcutAction};
|
|
use crate::state::AppState;
|
|
use super::{
|
|
CloseWaiter,
|
|
CliCredential,
|
|
CliRequest,
|
|
CliResponse,
|
|
Stream,
|
|
};
|
|
|
|
|
|
pub fn serve(app_handle: AppHandle) -> std::io::Result<()> {
|
|
super::serve("creddy-server", app_handle, handle)
|
|
}
|
|
|
|
|
|
async fn handle(
|
|
mut stream: Stream,
|
|
app_handle: AppHandle,
|
|
client_pid: u32
|
|
) -> Result<(), HandlerError> {
|
|
// read from stream until delimiter is reached
|
|
let mut buf: Vec<u8> = Vec::with_capacity(1024); // requests are small, 1KiB is more than enough
|
|
let mut n = 0;
|
|
loop {
|
|
n += stream.read_buf(&mut buf).await?;
|
|
if let Some(&b'\n') = buf.last() {
|
|
break;
|
|
}
|
|
// sanity check, no request should ever be within a mile of 1MB
|
|
else if n >= (1024 * 1024) {
|
|
return Err(HandlerError::RequestTooLarge);
|
|
}
|
|
}
|
|
|
|
let client = clientinfo::get_client(client_pid, true)?;
|
|
let waiter = CloseWaiter { stream: &mut stream };
|
|
|
|
|
|
let req: CliRequest = serde_json::from_slice(&buf)?;
|
|
let res = match req {
|
|
CliRequest::GetAwsCredential{ name, base } => get_aws_credentials(
|
|
name, base, client, app_handle, waiter
|
|
).await,
|
|
CliRequest::GetDockerCredential{ server_url } => get_docker_credential (
|
|
server_url, client, app_handle, waiter
|
|
).await,
|
|
CliRequest::StoreDockerCredential(docker_credential) => store_docker_credential(
|
|
docker_credential, app_handle, client, waiter
|
|
).await,
|
|
CliRequest::EraseDockerCredential { server_url } => erase_docker_credential(
|
|
server_url, app_handle, client, waiter
|
|
).await,
|
|
CliRequest::InvokeShortcut{ action } => invoke_shortcut(action).await,
|
|
};
|
|
|
|
// doesn't make sense to send the error to the client if the client has already left
|
|
if let Err(HandlerError::Abandoned) = res {
|
|
return Err(HandlerError::Abandoned);
|
|
}
|
|
|
|
let res = serde_json::to_vec(&res).unwrap();
|
|
stream.write_all(&res).await?;
|
|
Ok(())
|
|
}
|
|
|
|
|
|
async fn invoke_shortcut(action: ShortcutAction) -> Result<CliResponse, HandlerError> {
|
|
shortcuts::exec_shortcut(action);
|
|
Ok(CliResponse::Empty)
|
|
}
|
|
|
|
|
|
async fn get_aws_credentials(
|
|
name: Option<String>,
|
|
base: bool,
|
|
client: Client,
|
|
app_handle: AppHandle,
|
|
waiter: CloseWaiter<'_>,
|
|
) -> Result<CliResponse, HandlerError> {
|
|
let detail = RequestNotificationDetail::new_aws(client, name.clone(), base);
|
|
let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
|
|
match response.approval {
|
|
Approval::Approved => {
|
|
let state = app_handle.state::<AppState>();
|
|
if response.base {
|
|
let creds = state.get_aws_base(name).await?;
|
|
Ok(CliResponse::Credential(CliCredential::AwsBase(creds)))
|
|
}
|
|
else {
|
|
let creds = state.get_aws_session(name).await?.clone();
|
|
Ok(CliResponse::Credential(CliCredential::AwsSession(creds)))
|
|
}
|
|
},
|
|
Approval::Denied => Err(HandlerError::Denied),
|
|
}
|
|
}
|
|
|
|
async fn get_docker_credential(
|
|
server_url: String,
|
|
client: Client,
|
|
app_handle: AppHandle,
|
|
waiter: CloseWaiter<'_>,
|
|
) -> Result<CliResponse, HandlerError> {
|
|
let state = app_handle.state::<AppState>();
|
|
let meta = state.docker_credential_meta(&server_url).await.unwrap_or(None);
|
|
if meta.is_none() {
|
|
return Err(
|
|
HandlerError::NoCredentials(
|
|
GetCredentialsError::Load(
|
|
LoadCredentialsError::NoCredentials
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
let detail = RequestNotificationDetail::new_docker(
|
|
RequestAction::Access,
|
|
client,
|
|
server_url.clone()
|
|
);
|
|
let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
|
|
match response.approval {
|
|
Approval::Approved => {
|
|
let creds = state.get_docker_credential(&server_url).await?;
|
|
Ok(CliResponse::Credential(CliCredential::Docker(creds)))
|
|
},
|
|
Approval::Denied => {
|
|
Err(HandlerError::Denied)
|
|
},
|
|
}
|
|
}
|
|
|
|
async fn store_docker_credential(
|
|
docker_credential: DockerCredential,
|
|
app_handle: AppHandle,
|
|
client: Client,
|
|
waiter: CloseWaiter<'_>,
|
|
) -> Result<CliResponse, HandlerError> {
|
|
let state = app_handle.state::<AppState>();
|
|
|
|
// We want to do this before asking for confirmation from the user, because Docker has an annoying
|
|
// habit of calling `get` and then immediately turning around and calling `store` with the same
|
|
// data. In that case we want to avoid asking for confirmation at all.
|
|
match state.get_docker_credential(&docker_credential.server_url).await {
|
|
// if there is already a credential with this server_url, and it is unchanged, we're done
|
|
Ok(c) if c == docker_credential => return Ok(CliResponse::Empty),
|
|
// otherwise we are making an update, so proceed
|
|
Ok(_) => (),
|
|
// if the app is locked, then this isn't the situation described above, so proceed
|
|
Err(GetCredentialsError::Locked) => (),
|
|
// if the app is unlocked, and there is no matching credential, proceed
|
|
Err(GetCredentialsError::Load(LoadCredentialsError::NoCredentials)) => (),
|
|
// any other error is a failure
|
|
Err(e) => return Err(e.into()),
|
|
};
|
|
|
|
let detail = RequestNotificationDetail::new_docker(
|
|
RequestAction::Save,
|
|
client,
|
|
docker_credential.server_url.clone(),
|
|
);
|
|
let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
|
|
if matches!(response.approval, Approval::Denied) {
|
|
return Err(HandlerError::Denied);
|
|
}
|
|
|
|
let (id, name) = state.docker_credential_meta(&docker_credential.server_url)
|
|
.await
|
|
.map_err(|e| GetCredentialsError::Load(e))?
|
|
.unwrap_or_else(|| (credentials::random_uuid(), docker_credential.server_url.clone()));
|
|
|
|
let record = CredentialRecord {
|
|
id,
|
|
name,
|
|
is_default: false,
|
|
credential: Credential::Docker(docker_credential)
|
|
};
|
|
state.save_credential(record).await?;
|
|
|
|
Ok(CliResponse::Empty)
|
|
}
|
|
|
|
async fn erase_docker_credential(
|
|
server_url: String,
|
|
app_handle: AppHandle,
|
|
client: Client,
|
|
waiter: CloseWaiter<'_>
|
|
) -> Result<CliResponse, HandlerError> {
|
|
let state = app_handle.state::<AppState>();
|
|
|
|
let detail = RequestNotificationDetail::new_docker(
|
|
RequestAction::Delete,
|
|
client,
|
|
server_url.clone(),
|
|
);
|
|
let resp = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
|
|
match resp.approval {
|
|
Approval::Approved => {
|
|
state.delete_credential_by_name(&server_url).await?;
|
|
Ok(CliResponse::Empty)
|
|
}
|
|
Approval::Denied => {
|
|
Err(HandlerError::Denied)
|
|
}
|
|
}
|
|
}
|