Skip to content

Commit d457d09

Browse files
committed
WIP: portal impl
1 parent 008fc56 commit d457d09

File tree

5 files changed

+251
-0
lines changed

5 files changed

+251
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
2+
/oo7-portal-impl/target
23
Cargo.lock

oo7-portal-impl/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "oo7-portal-impl"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
async-std = { version = "1.11", features = [ "attributes" ] }
8+
futures-channel = "0.3"
9+
oo7 = { path = "../" }
10+
ring = "0.17.5"
11+
secrecy = { version = "0.8", features = [ "alloc" ] }
12+
serde = {version = "1.0", features = ["derive"]}
13+
tracing = "0.1"
14+
tracing-subscriber = "0.3"
15+
zbus = "3.5.0"
16+
zeroize = { version = "1", features = ["zeroize_derive"] }
17+
futures = "0.3"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[portal]
2+
DBusName=org.freedesktop.impl.portal.desktop.oo7
3+
Interfaces=org.freedesktop.impl.portal.Secret
4+
UseIn=gnome
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[D-BUS Service]
2+
Name=org.freedesktop.impl.portal.Secret
3+
Exec=@bindir@/oo7-portal-impl

oo7-portal-impl/src/main.rs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
use std::borrow::BorrowMut;
2+
use std::collections::HashMap;
3+
use std::os::fd::AsRawFd;
4+
use std::os::fd::FromRawFd;
5+
use std::sync::{Arc, Mutex};
6+
7+
use async_std::io::WriteExt;
8+
use futures::FutureExt;
9+
use oo7::dbus::{Algorithm, Service};
10+
use ring::rand::SecureRandom;
11+
use serde::Serialize;
12+
use std::future::pending;
13+
use zbus::dbus_interface;
14+
use zbus::zvariant;
15+
use zbus::zvariant::Type;
16+
use zvariant::OwnedObjectPath;
17+
18+
const PORTAL_SECRET_SIZE: usize = 64;
19+
const NAME: &str = "org.freedesktop.impl.portal.desktop.oo7";
20+
const PATH: &str = "/org/freedesktop/portal/desktop";
21+
22+
#[derive(Serialize, PartialEq, Eq, Debug, Type)]
23+
#[doc(hidden)]
24+
enum ResponseType {
25+
Success = 0,
26+
Cancelled = 1,
27+
Other = 2,
28+
}
29+
30+
#[derive(zbus::DBusError, Debug)]
31+
enum Error {
32+
Msg(String),
33+
}
34+
35+
impl Error {
36+
fn new(msg: &str) -> Error {
37+
Error::Msg(msg.to_string())
38+
}
39+
}
40+
41+
impl From<oo7::dbus::Error> for Error {
42+
fn from(err: oo7::dbus::Error) -> Self {
43+
Error::new(&err.to_string())
44+
}
45+
}
46+
47+
struct Secret;
48+
49+
#[dbus_interface(name = "org.freedesktop.impl.portal.Secret")]
50+
impl Secret {
51+
#[dbus_interface(property, name = "version")]
52+
fn version(&self) -> u32 {
53+
0
54+
}
55+
56+
#[dbus_interface(out_args("response", "results"))]
57+
async fn retrieve_secret(
58+
&self,
59+
#[zbus(object_server)] object_server: &zbus::ObjectServer,
60+
handle: OwnedObjectPath,
61+
app_id: &str,
62+
fd: zvariant::Fd,
63+
_options: HashMap<&str, zvariant::Value<'_>>,
64+
) -> Result<(ResponseType, HashMap<&str, zvariant::OwnedValue>), Error> {
65+
tracing::debug!("Got request from {app_id} with options: {_options:?}");
66+
67+
let (sender, receiver) = futures_channel::oneshot::channel();
68+
69+
if let Err(err) = Request::serve(object_server, handle.clone(), sender).await {
70+
tracing::error!("Could not register object {handle}: {err}");
71+
}
72+
73+
let fut_1 = async move {
74+
let res = match retrieve_secret_inner(app_id, fd).await {
75+
Ok(res) => Ok((ResponseType::Success, res)),
76+
Err(err) => {
77+
tracing::error!("could not retrieve secret: {err}");
78+
Ok((ResponseType::Other, HashMap::new()))
79+
}
80+
};
81+
82+
// We do not accept Close request anymore here.
83+
tracing::debug!("Object {handle} handled");
84+
object_server
85+
.remove::<Request, &zvariant::OwnedObjectPath>(&handle)
86+
.await
87+
.unwrap();
88+
89+
res
90+
};
91+
92+
let fut_2 = async move {
93+
receiver.await.unwrap();
94+
Ok((ResponseType::Cancelled, HashMap::new()))
95+
};
96+
97+
let t1 = fut_1.fuse();
98+
let t2 = fut_2.fuse();
99+
100+
futures::pin_mut!(t1, t2);
101+
102+
futures::select! {
103+
fut_1_res = t1 => fut_1_res,
104+
fut_2_res = t2 => fut_2_res,
105+
}
106+
}
107+
}
108+
109+
fn generate_secret() -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
110+
let mut secret = [0; PORTAL_SECRET_SIZE];
111+
let rand = ring::rand::SystemRandom::new();
112+
rand.fill(secret.borrow_mut())
113+
.map_err(|err| Error::new(&err.to_string()))?;
114+
Ok(zeroize::Zeroizing::new(secret.to_vec()))
115+
}
116+
117+
async fn retrieve_secret_inner(
118+
app_id: &str,
119+
fd: zvariant::Fd,
120+
) -> Result<HashMap<&'static str, zvariant::OwnedValue>, Error> {
121+
let service = Service::new(Algorithm::Encrypted)
122+
.await
123+
.or(Service::new(Algorithm::Plain).await)?;
124+
let collection = service.default_collection().await?;
125+
let attributes = HashMap::from([("app_id", app_id)]);
126+
127+
let secret = if let Some(item) = collection
128+
.search_items(attributes.clone())
129+
.await
130+
.map_err(|err| Error::new(&err.to_string()))?
131+
.first()
132+
{
133+
item.secret().await?
134+
} else {
135+
tracing::debug!("Could not find secret for {app_id}, creating one");
136+
let secret = generate_secret()?;
137+
collection
138+
.create_item(
139+
&format!("Secret Portal token for {app_id}"),
140+
attributes,
141+
&secret,
142+
true,
143+
// TODO Find a better one.
144+
"text/plain",
145+
)
146+
.await?;
147+
148+
secret
149+
};
150+
151+
// Write the secret to the FD.
152+
let raw_fd = fd.as_raw_fd();
153+
let mut stream = unsafe { async_std::os::unix::net::UnixStream::from_raw_fd(raw_fd) };
154+
stream
155+
.write_all(&secret)
156+
.await
157+
.map_err(|e| Error::new(&e.to_string()))?;
158+
159+
Ok(HashMap::new())
160+
}
161+
162+
#[async_std::main]
163+
async fn main() -> Result<(), zbus::Error> {
164+
tracing_subscriber::fmt::init();
165+
166+
let backend = Secret;
167+
let cnx = zbus::ConnectionBuilder::session()?
168+
// .name(NAME)?
169+
.serve_at(PATH, backend)?
170+
.build()
171+
.await?;
172+
// NOTE For debugging.
173+
let flags = zbus::fdo::RequestNameFlags::ReplaceExisting
174+
| zbus::fdo::RequestNameFlags::AllowReplacement;
175+
cnx.request_name_with_flags(NAME, flags).await?;
176+
177+
loop {
178+
pending::<()>().await;
179+
}
180+
}
181+
182+
struct Request {
183+
handle_path: zvariant::OwnedObjectPath,
184+
sender: Arc<Mutex<Option<futures_channel::oneshot::Sender<()>>>>,
185+
}
186+
187+
#[dbus_interface(name = "org.freedesktop.impl.portal.Request")]
188+
impl Request {
189+
async fn close(
190+
&self,
191+
#[zbus(object_server)] server: &zbus::ObjectServer,
192+
) -> zbus::fdo::Result<()> {
193+
tracing::debug!("Object {} closed", self.handle_path);
194+
server
195+
.remove::<Self, &zvariant::OwnedObjectPath>(&self.handle_path)
196+
.await?;
197+
198+
if let Ok(mut guard) = self.sender.lock() {
199+
if let Some(sender) = (*guard).take() {
200+
// This will Err out if the receiver has been dropped.
201+
let _ = sender.send(());
202+
}
203+
}
204+
205+
Ok(())
206+
}
207+
}
208+
209+
impl Request {
210+
async fn serve(
211+
object_server: &zbus::ObjectServer,
212+
handle_path: OwnedObjectPath,
213+
sender: futures_channel::oneshot::Sender<()>,
214+
) -> zbus::fdo::Result<()> {
215+
tracing::debug!("Handling object {:?}", handle_path.as_str());
216+
217+
let iface = Request {
218+
handle_path: handle_path.clone(),
219+
sender: Arc::new(Mutex::new(Some(sender))),
220+
};
221+
222+
object_server.at(handle_path, iface).await?;
223+
224+
Ok(())
225+
}
226+
}

0 commit comments

Comments
 (0)