linux: add credentials impl via oo7 (#8035)

This change implements gpui's credentials API for the linux platform,
using the [`oo7`](https://lib.rs/crates/oo7) library.

We had a short discussion on Discord about where to store credentials
and landed on the two dbus APIs
[`org.freedesktop.Secrets`](https://specifications.freedesktop.org/secret-service/latest/index.html)
and
[`org.freedesktop.portal.Secrets`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Secret.html).
The first one provides access to a more or less general purpose
keystore, the second provides a way of obtaining a unique masterkey
which in turn can be used for encrypting stuff and storing it to disk
(especially interesting for sandboxed apps, think flatpak/snap).

I decided to give the implementation a try with `oo7`, which uses the
portal if the app is sandboxed and the secret service otherwise. If we
do not want to use that library, we would probably have to more or less
copy its functionality anyways. I also heard rumors of eventually
changing the credentials API and I think this implementation serves as a
starting point to discuss the need for this?

With a working credentials implementation the sign in button now works
(it panicked before).

Todos:
- [x] implement keystore unlocking
- [x] try the change with oo7's tracing enabled?
- [x] test the password deletion

Release Notes:

- N/A

---------

Signed-off-by: Niklas Wimmer <mail@nwimmer.me>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Niklas Wimmer 2024-03-03 22:54:06 +01:00 committed by GitHub
parent 1442fcb497
commit ff65008316
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 531 additions and 163 deletions

625
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -113,6 +113,7 @@ xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
as-raw-xcb-connection = "1"
calloop = "0.12.4"
calloop-wayland-source = "0.2.0"
oo7 = "0.3.0"
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View File

@ -9,6 +9,7 @@ use std::{
time::Duration,
};
use anyhow::anyhow;
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
use async_task::Runnable;
use calloop::{EventLoop, LoopHandle, LoopSignal};
@ -107,6 +108,8 @@ impl LinuxPlatform {
}
}
const KEYRING_LABEL: &str = "zed-github-account";
impl Platform for LinuxPlatform {
fn background_executor(&self) -> BackgroundExecutor {
self.inner.background_executor.clone()
@ -361,23 +364,70 @@ impl Platform for LinuxPlatform {
//todo!(linux)
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
Task::Ready(Some(Err(anyhow::Error::msg(
"Platform<LinuxPlatform>::with_credentials is not implemented yet",
))))
let url = url.to_string();
let username = username.to_string();
let password = password.to_vec();
self.background_executor().spawn(async move {
let keyring = oo7::Keyring::new().await?;
keyring.unlock().await?;
keyring
.create_item(
KEYRING_LABEL,
&vec![("url", &url), ("username", &username)],
password,
true,
)
.await?;
Ok(())
})
}
//todo!(linux)
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
Task::Ready(Some(Err(anyhow::Error::msg(
"Platform<LinuxPlatform>::read_credentials is not implemented yet",
))))
let url = url.to_string();
self.background_executor().spawn(async move {
let keyring = oo7::Keyring::new().await?;
keyring.unlock().await?;
let items = keyring.search_items(&vec![("url", &url)]).await?;
for item in items.into_iter() {
if item.label().await.is_ok_and(|label| label == KEYRING_LABEL) {
let attributes = item.attributes().await?;
let username = attributes
.get("username")
.ok_or_else(|| anyhow!("Cannot find username in stored credentials"))?;
let secret = item.secret().await?;
// we lose the zeroizing capabilities at this boundary,
// a current limitation GPUI's credentials api
return Ok(Some((username.to_string(), secret.to_vec())));
} else {
continue;
}
}
Ok(None)
})
}
//todo!(linux)
fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
Task::Ready(Some(Err(anyhow::Error::msg(
"Platform<LinuxPlatform>::delete_credentials is not implemented yet",
))))
let url = url.to_string();
self.background_executor().spawn(async move {
let keyring = oo7::Keyring::new().await?;
keyring.unlock().await?;
let items = keyring.search_items(&vec![("url", &url)]).await?;
for item in items.into_iter() {
if item.label().await.is_ok_and(|label| label == KEYRING_LABEL) {
item.delete().await?;
return Ok(());
}
}
Ok(())
})
}
fn window_appearance(&self) -> crate::WindowAppearance {