cargo_credential_libsecret/
lib.rs1#[cfg(target_os = "linux")]
6mod linux {
7 use anyhow::Context;
10 use cargo_credential::{
11 Action, CacheControl, Credential, CredentialResponse, Error, RegistryInfo, Secret,
12 read_token,
13 };
14 use libloading::{Library, Symbol};
15 use std::ffi::{CStr, CString};
16 use std::os::raw::{c_char, c_int};
17 use std::ptr::{null, null_mut};
18
19 #[allow(non_camel_case_types)]
20 type gchar = c_char;
21
22 #[allow(non_camel_case_types)]
23 type gboolean = c_int;
24
25 #[allow(non_camel_case_types)]
26 type gint = c_int;
27
28 #[allow(non_camel_case_types)]
29 type gpointer = *mut ();
30
31 type GQuark = u32;
32
33 #[repr(C)]
34 struct GError {
35 domain: GQuark,
36 code: c_int,
37 message: *mut gchar,
38 }
39
40 #[repr(C)]
41 struct GCancellable {
42 _private: [u8; 0],
43 }
44
45 #[repr(C)]
46 struct SecretSchema {
47 name: *const gchar,
48 flags: SecretSchemaFlags,
49 attributes: [SecretSchemaAttribute; 32],
50 reserved: gint,
51 reserved1: gpointer,
52 reserved2: gpointer,
53 reserved3: gpointer,
54 reserved4: gpointer,
55 reserved5: gpointer,
56 reserved6: gpointer,
57 reserved7: gpointer,
58 }
59
60 #[repr(C)]
61 #[derive(Copy, Clone)]
62 struct SecretSchemaAttribute {
63 name: *const gchar,
64 attr_type: SecretSchemaAttributeType,
65 }
66
67 #[repr(C)]
68 enum SecretSchemaFlags {
69 None = 0,
70 }
71
72 #[repr(C)]
73 #[derive(Copy, Clone)]
74 enum SecretSchemaAttributeType {
75 String = 0,
76 }
77
78 type SecretPasswordStoreSync = extern "C" fn(
79 schema: *const SecretSchema,
80 collection: *const gchar,
81 label: *const gchar,
82 password: *const gchar,
83 cancellable: *mut GCancellable,
84 error: *mut *mut GError,
85 ...
86 ) -> gboolean;
87 type SecretPasswordClearSync = extern "C" fn(
88 schema: *const SecretSchema,
89 cancellable: *mut GCancellable,
90 error: *mut *mut GError,
91 ...
92 ) -> gboolean;
93 type SecretPasswordLookupSync = extern "C" fn(
94 schema: *const SecretSchema,
95 cancellable: *mut GCancellable,
96 error: *mut *mut GError,
97 ...
98 ) -> *mut gchar;
99
100 pub struct LibSecretCredential {
101 libsecret: Library,
102 }
103
104 fn label(index_url: &str) -> CString {
105 CString::new(format!("cargo-registry:{}", index_url)).unwrap()
106 }
107
108 fn schema() -> SecretSchema {
109 let mut attributes = [SecretSchemaAttribute {
110 name: null(),
111 attr_type: SecretSchemaAttributeType::String,
112 }; 32];
113 attributes[0] = SecretSchemaAttribute {
114 name: c"url".as_ptr() as *const gchar,
115 attr_type: SecretSchemaAttributeType::String,
116 };
117 SecretSchema {
118 name: c"org.rust-lang.cargo.registry".as_ptr() as *const gchar,
119 flags: SecretSchemaFlags::None,
120 attributes,
121 reserved: 0,
122 reserved1: null_mut(),
123 reserved2: null_mut(),
124 reserved3: null_mut(),
125 reserved4: null_mut(),
126 reserved5: null_mut(),
127 reserved6: null_mut(),
128 reserved7: null_mut(),
129 }
130 }
131
132 impl LibSecretCredential {
133 pub fn new() -> Result<LibSecretCredential, Error> {
134 let libsecret = unsafe { Library::new("libsecret-1.so.0") }.context(
137 "failed to load libsecret: try installing the `libsecret` \
138 or `libsecret-1-0` package with the system package manager",
139 )?;
140 Ok(Self { libsecret })
141 }
142 }
143
144 impl Credential for &LibSecretCredential {
145 fn perform(
146 &self,
147 registry: &RegistryInfo<'_>,
148 action: &Action<'_>,
149 _args: &[&str],
150 ) -> Result<CredentialResponse, Error> {
151 let secret_password_lookup_sync: Symbol<'_, SecretPasswordLookupSync>;
152 let secret_password_store_sync: Symbol<'_, SecretPasswordStoreSync>;
153 let secret_password_clear_sync: Symbol<'_, SecretPasswordClearSync>;
154 unsafe {
155 secret_password_lookup_sync = self
156 .libsecret
157 .get(b"secret_password_lookup_sync\0")
158 .map_err(Box::new)?;
159 secret_password_store_sync = self
160 .libsecret
161 .get(b"secret_password_store_sync\0")
162 .map_err(Box::new)?;
163 secret_password_clear_sync = self
164 .libsecret
165 .get(b"secret_password_clear_sync\0")
166 .map_err(Box::new)?;
167 }
168
169 let index_url_c = CString::new(registry.index_url).unwrap();
170 let mut error: *mut GError = null_mut();
171 let attr_url = c"url".as_ptr() as *const gchar;
172 let schema = schema();
173 match action {
174 cargo_credential::Action::Get(_) => unsafe {
175 let token_c = secret_password_lookup_sync(
176 &schema,
177 null_mut(),
178 &mut error,
179 attr_url,
180 index_url_c.as_ptr(),
181 null() as *const gchar,
182 );
183 if !error.is_null() {
184 return Err(format!(
185 "failed to get token: {}",
186 CStr::from_ptr((*error).message)
187 .to_str()
188 .unwrap_or_default()
189 )
190 .into());
191 }
192 if token_c.is_null() {
193 return Err(Error::NotFound);
194 }
195 let token = Secret::from(
196 CStr::from_ptr(token_c)
197 .to_str()
198 .map_err(|e| format!("expected utf8 token: {}", e))?
199 .to_string(),
200 );
201 Ok(CredentialResponse::Get {
202 token,
203 cache: CacheControl::Session,
204 operation_independent: true,
205 })
206 },
207 cargo_credential::Action::Login(options) => {
208 let label = label(registry.name.unwrap_or(registry.index_url));
209 let token = CString::new(read_token(options, registry)?.expose()).unwrap();
210 unsafe {
211 secret_password_store_sync(
212 &schema,
213 c"default".as_ptr() as *const gchar,
214 label.as_ptr(),
215 token.as_ptr(),
216 null_mut(),
217 &mut error,
218 attr_url,
219 index_url_c.as_ptr(),
220 null() as *const gchar,
221 );
222 if !error.is_null() {
223 return Err(format!(
224 "failed to store token: {}",
225 CStr::from_ptr((*error).message)
226 .to_str()
227 .unwrap_or_default()
228 )
229 .into());
230 }
231 }
232 Ok(CredentialResponse::Login)
233 }
234 cargo_credential::Action::Logout => {
235 unsafe {
236 secret_password_clear_sync(
237 &schema,
238 null_mut(),
239 &mut error,
240 attr_url,
241 index_url_c.as_ptr(),
242 null() as *const gchar,
243 );
244 if !error.is_null() {
245 return Err(format!(
246 "failed to erase token: {}",
247 CStr::from_ptr((*error).message)
248 .to_str()
249 .unwrap_or_default()
250 )
251 .into());
252 }
253 }
254 Ok(CredentialResponse::Logout)
255 }
256 _ => Err(Error::OperationNotSupported),
257 }
258 }
259 }
260}
261
262#[cfg(not(target_os = "linux"))]
263pub use cargo_credential::UnsupportedCredential as LibSecretCredential;
264#[cfg(target_os = "linux")]
265pub use linux::LibSecretCredential;