binaryninja/collaboration/
remote.rs

1use super::{sync, GroupId, RemoteGroup, RemoteProject, RemoteUser};
2use binaryninjacore_sys::*;
3use std::env::VarError;
4use std::ffi::c_void;
5use std::ptr::NonNull;
6
7use crate::binary_view::BinaryView;
8use crate::database::Database;
9use crate::enterprise;
10use crate::progress::{NoProgressCallback, ProgressCallback};
11use crate::project::Project;
12use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
13use crate::secrets_provider::CoreSecretsProvider;
14use crate::settings::Settings;
15use crate::string::{BnString, IntoCStr};
16
17#[repr(transparent)]
18pub struct Remote {
19    pub(crate) handle: NonNull<BNRemote>,
20}
21
22impl Remote {
23    pub(crate) unsafe fn from_raw(handle: NonNull<BNRemote>) -> Self {
24        Self { handle }
25    }
26
27    pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNRemote>) -> Ref<Self> {
28        Ref::new(Self { handle })
29    }
30
31    /// Create a Remote and add it to the list of known remotes (saved to Settings)
32    pub fn new(name: &str, address: &str) -> Ref<Self> {
33        let name = name.to_cstr();
34        let address = address.to_cstr();
35        let result = unsafe { BNCollaborationCreateRemote(name.as_ptr(), address.as_ptr()) };
36        unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) }
37    }
38
39    /// Get the Remote for a Database
40    pub fn get_for_local_database(database: &Database) -> Result<Option<Ref<Remote>>, ()> {
41        sync::get_remote_for_local_database(database)
42    }
43
44    /// Get the Remote for a Binary View
45    pub fn get_for_binary_view(bv: &BinaryView) -> Result<Option<Ref<Remote>>, ()> {
46        sync::get_remote_for_binary_view(bv)
47    }
48
49    /// Checks if the remote has pulled metadata like its id, etc.
50    pub fn has_loaded_metadata(&self) -> bool {
51        unsafe { BNRemoteHasLoadedMetadata(self.handle.as_ptr()) }
52    }
53
54    /// Gets the unique id. If metadata has not been pulled, it will be pulled upon calling this.
55    pub fn unique_id(&self) -> Result<BnString, ()> {
56        if !self.has_loaded_metadata() {
57            self.load_metadata()?;
58        }
59        let result = unsafe { BNRemoteGetUniqueId(self.handle.as_ptr()) };
60        assert!(!result.is_null());
61        Ok(unsafe { BnString::from_raw(result) })
62    }
63
64    /// Gets the name of the remote.
65    pub fn name(&self) -> String {
66        let result = unsafe { BNRemoteGetName(self.handle.as_ptr()) };
67        assert!(!result.is_null());
68        unsafe { BnString::into_string(result) }
69    }
70
71    /// Gets the address of the remote.
72    pub fn address(&self) -> String {
73        let result = unsafe { BNRemoteGetAddress(self.handle.as_ptr()) };
74        assert!(!result.is_null());
75        unsafe { BnString::into_string(result) }
76    }
77
78    /// Checks if the remote is connected.
79    pub fn is_connected(&self) -> bool {
80        unsafe { BNRemoteIsConnected(self.handle.as_ptr()) }
81    }
82
83    /// Gets the username used to connect to the remote.
84    pub fn username(&self) -> String {
85        let result = unsafe { BNRemoteGetUsername(self.handle.as_ptr()) };
86        assert!(!result.is_null());
87        unsafe { BnString::into_string(result) }
88    }
89
90    /// Gets the token used to connect to the remote.
91    pub fn token(&self) -> String {
92        let result = unsafe { BNRemoteGetToken(self.handle.as_ptr()) };
93        assert!(!result.is_null());
94        unsafe { BnString::into_string(result) }
95    }
96
97    /// Gets the server version. If metadata has not been pulled, it will be pulled upon calling this.
98    pub fn server_version(&self) -> Result<i32, ()> {
99        if !self.has_loaded_metadata() {
100            self.load_metadata()?;
101        }
102        Ok(unsafe { BNRemoteGetServerVersion(self.handle.as_ptr()) })
103    }
104
105    /// Gets the server build id. If metadata has not been pulled, it will be pulled upon calling this.
106    pub fn server_build_id(&self) -> Result<BnString, ()> {
107        if !self.has_loaded_metadata() {
108            self.load_metadata()?;
109        }
110        unsafe {
111            Ok(BnString::from_raw(BNRemoteGetServerBuildId(
112                self.handle.as_ptr(),
113            )))
114        }
115    }
116
117    /// Gets the list of supported authentication backends on the server.
118    /// If metadata has not been pulled, it will be pulled upon calling this.
119    pub fn auth_backends(&self) -> Result<(Array<BnString>, Array<BnString>), ()> {
120        if !self.has_loaded_metadata() {
121            self.load_metadata()?;
122        }
123
124        let mut backend_ids = std::ptr::null_mut();
125        let mut backend_names = std::ptr::null_mut();
126        let mut count = 0;
127        let success = unsafe {
128            BNRemoteGetAuthBackends(
129                self.handle.as_ptr(),
130                &mut backend_ids,
131                &mut backend_names,
132                &mut count,
133            )
134        };
135        success
136            .then(|| unsafe {
137                (
138                    Array::new(backend_ids, count, ()),
139                    Array::new(backend_names, count, ()),
140                )
141            })
142            .ok_or(())
143    }
144
145    /// Checks if the current user is an administrator.
146    pub fn is_admin(&self) -> Result<bool, ()> {
147        if !self.has_pulled_users() {
148            self.pull_users()?;
149        }
150        Ok(unsafe { BNRemoteIsAdmin(self.handle.as_ptr()) })
151    }
152
153    /// Checks if the remote is the same as the Enterprise License server.
154    pub fn is_enterprise(&self) -> Result<bool, ()> {
155        if !self.has_loaded_metadata() {
156            self.load_metadata()?;
157        }
158        Ok(unsafe { BNRemoteIsEnterprise(self.handle.as_ptr()) })
159    }
160
161    /// Loads metadata from the remote, including unique id and versions.
162    pub fn load_metadata(&self) -> Result<(), ()> {
163        let success = unsafe { BNRemoteLoadMetadata(self.handle.as_ptr()) };
164        success.then_some(()).ok_or(())
165    }
166
167    /// Requests an authentication token using a username and password.
168    pub fn request_authentication_token(&self, username: &str, password: &str) -> Option<String> {
169        let username = username.to_cstr();
170        let password = password.to_cstr();
171        let token = unsafe {
172            BNRemoteRequestAuthenticationToken(
173                self.handle.as_ptr(),
174                username.as_ptr(),
175                password.as_ptr(),
176            )
177        };
178        if token.is_null() {
179            None
180        } else {
181            Some(unsafe { BnString::into_string(token) })
182        }
183    }
184
185    /// Connects to the Remote, loading metadata and optionally acquiring a token.
186    ///
187    /// Use [Remote::connect_with_opts] if you cannot otherwise automatically connect using enterprise.
188    ///
189    /// WARNING: This is currently **not** thread safe, if you try and connect/disconnect to a remote on
190    /// multiple threads, you will be subject to race conditions. To avoid this, wrap the [`Remote`] in
191    /// a synchronization primitive and pass that to your threads. Or don't try and connect on multiple threads.
192    pub fn connect(&self) -> Result<(), ()> {
193        if self.is_enterprise()? && enterprise::is_server_authenticated() {
194            self.connect_with_opts(ConnectionOptions::from_enterprise()?)
195        } else {
196            // Try to load from env vars.
197            match ConnectionOptions::from_env_variables() {
198                Ok(connection_opts) => self.connect_with_opts(connection_opts),
199                Err(_) => {
200                    // Try to load from the enterprise secrets provider.
201                    let secrets_connection_opts =
202                        ConnectionOptions::from_secrets_provider(&self.address())?;
203                    self.connect_with_opts(secrets_connection_opts)
204                }
205            }
206        }
207    }
208
209    // TODO: This needs docs and proper error.
210    pub fn connect_with_opts(&self, options: ConnectionOptions) -> Result<(), ()> {
211        // TODO: Should we make used load metadata first?
212        if !self.has_loaded_metadata() {
213            self.load_metadata()?;
214        }
215        let token = match options.token {
216            Some(token) => token,
217            None => {
218                // TODO: If password not defined than error saying no token or password
219                let password = options
220                    .password
221                    .expect("No password or token for connection!");
222                let token = self.request_authentication_token(&options.username, &password);
223                // TODO: Error if None.
224                token.unwrap().to_string()
225            }
226        };
227        let username = options.username.to_cstr();
228        let token = token.to_cstr();
229        let success =
230            unsafe { BNRemoteConnect(self.handle.as_ptr(), username.as_ptr(), token.as_ptr()) };
231        success.then_some(()).ok_or(())
232    }
233
234    /// Disconnects from the remote.
235    ///
236    /// WARNING: This is currently **not** thread safe, if you try and connect/disconnect to a remote on
237    /// multiple threads you will be subject to race conditions. To avoid this wrap the [`Remote`] in
238    /// a synchronization primitive, and pass that to your threads. Or don't try and connect on multiple threads.
239    pub fn disconnect(&self) -> Result<(), ()> {
240        let success = unsafe { BNRemoteDisconnect(self.handle.as_ptr()) };
241        success.then_some(()).ok_or(())
242    }
243
244    /// Checks if the project has pulled the projects yet.
245    pub fn has_pulled_projects(&self) -> bool {
246        unsafe { BNRemoteHasPulledProjects(self.handle.as_ptr()) }
247    }
248
249    /// Checks if the project has pulled the groups yet.
250    pub fn has_pulled_groups(&self) -> bool {
251        unsafe { BNRemoteHasPulledGroups(self.handle.as_ptr()) }
252    }
253
254    /// Checks if the project has pulled the users yet.
255    pub fn has_pulled_users(&self) -> bool {
256        unsafe { BNRemoteHasPulledUsers(self.handle.as_ptr()) }
257    }
258
259    /// Gets the list of projects in this project.
260    ///
261    /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
262    pub fn projects(&self) -> Result<Array<RemoteProject>, ()> {
263        if !self.has_pulled_projects() {
264            self.pull_projects()?;
265        }
266
267        let mut count = 0;
268        let value = unsafe { BNRemoteGetProjects(self.handle.as_ptr(), &mut count) };
269        if value.is_null() {
270            return Err(());
271        }
272        Ok(unsafe { Array::new(value, count, ()) })
273    }
274
275    /// Gets a specific project in the Remote by its id.
276    ///
277    /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
278    pub fn get_project_by_id(&self, id: &str) -> Result<Option<Ref<RemoteProject>>, ()> {
279        if !self.has_pulled_projects() {
280            self.pull_projects()?;
281        }
282
283        let id = id.to_cstr();
284        let value = unsafe { BNRemoteGetProjectById(self.handle.as_ptr(), id.as_ptr()) };
285        Ok(NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) }))
286    }
287
288    /// Gets a specific project in the Remote by its name.
289    ///
290    /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
291    pub fn get_project_by_name(&self, name: &str) -> Result<Option<Ref<RemoteProject>>, ()> {
292        if !self.has_pulled_projects() {
293            self.pull_projects()?;
294        }
295
296        let name = name.to_cstr();
297        let value = unsafe { BNRemoteGetProjectByName(self.handle.as_ptr(), name.as_ptr()) };
298        Ok(NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) }))
299    }
300
301    /// Pulls the list of projects from the Remote.
302    pub fn pull_projects(&self) -> Result<(), ()> {
303        self.pull_projects_with_progress(NoProgressCallback)
304    }
305
306    /// Pulls the list of projects from the Remote.
307    ///
308    /// # Arguments
309    ///
310    /// * `progress` - Function to call for progress updates
311    pub fn pull_projects_with_progress<F: ProgressCallback>(
312        &self,
313        mut progress: F,
314    ) -> Result<(), ()> {
315        if !self.has_loaded_metadata() {
316            self.load_metadata()?;
317        }
318
319        let success = unsafe {
320            BNRemotePullProjects(
321                self.handle.as_ptr(),
322                Some(F::cb_progress_callback),
323                &mut progress as *mut F as *mut c_void,
324            )
325        };
326        success.then_some(()).ok_or(())
327    }
328
329    /// Creates a new project on the remote (and pull it).
330    ///
331    /// # Arguments
332    ///
333    /// * `name` - Project name
334    /// * `description` - Project description
335    pub fn create_project(&self, name: &str, description: &str) -> Result<Ref<RemoteProject>, ()> {
336        // TODO: Do we want this?
337        // TODO: If you have not yet pulled projects you will have never filled the map you will be placing your
338        // TODO: New project in.
339        if !self.has_pulled_projects() {
340            self.pull_projects()?;
341        }
342        let name = name.to_cstr();
343        let description = description.to_cstr();
344        let value = unsafe {
345            BNRemoteCreateProject(self.handle.as_ptr(), name.as_ptr(), description.as_ptr())
346        };
347        NonNull::new(value)
348            .map(|handle| unsafe { RemoteProject::ref_from_raw(handle) })
349            .ok_or(())
350    }
351
352    /// Create a new project on the remote from a local project.
353    pub fn import_local_project(&self, project: &Project) -> Option<Ref<RemoteProject>> {
354        self.import_local_project_with_progress(project, NoProgressCallback)
355    }
356
357    /// Create a new project on the remote from a local project.
358    pub fn import_local_project_with_progress<P: ProgressCallback>(
359        &self,
360        project: &Project,
361        mut progress: P,
362    ) -> Option<Ref<RemoteProject>> {
363        let value = unsafe {
364            BNRemoteImportLocalProject(
365                self.handle.as_ptr(),
366                project.handle.as_ptr(),
367                Some(P::cb_progress_callback),
368                &mut progress as *mut P as *mut c_void,
369            )
370        };
371        NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) })
372    }
373
374    /// Pushes an updated Project object to the Remote.
375    ///
376    /// # Arguments
377    ///
378    /// * `project` - Project object which has been updated
379    /// * `extra_fields` - Extra HTTP fields to send with the update
380    pub fn push_project<I>(&self, project: &RemoteProject, extra_fields: I) -> Result<(), ()>
381    where
382        I: IntoIterator<Item = (String, String)>,
383    {
384        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
385            .into_iter()
386            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
387            .unzip();
388        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
389        let mut values_raw = values.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
390
391        let success = unsafe {
392            BNRemotePushProject(
393                self.handle.as_ptr(),
394                project.handle.as_ptr(),
395                keys_raw.as_mut_ptr(),
396                values_raw.as_mut_ptr(),
397                keys_raw.len(),
398            )
399        };
400        success.then_some(()).ok_or(())
401    }
402
403    /// Deletes a project from the remote.
404    pub fn delete_project(&self, project: &RemoteProject) -> Result<(), ()> {
405        let success =
406            unsafe { BNRemoteDeleteProject(self.handle.as_ptr(), project.handle.as_ptr()) };
407        success.then_some(()).ok_or(())
408    }
409
410    /// Gets the list of groups in this project.
411    ///
412    /// If groups have not been pulled, they will be pulled upon calling this.
413    /// This function is only available to accounts with admin status on the Remote.
414    pub fn groups(&self) -> Result<Array<RemoteGroup>, ()> {
415        if !self.has_pulled_groups() {
416            self.pull_groups()?;
417        }
418
419        let mut count = 0;
420        let value = unsafe { BNRemoteGetGroups(self.handle.as_ptr(), &mut count) };
421        if value.is_null() {
422            return Err(());
423        }
424        Ok(unsafe { Array::new(value, count, ()) })
425    }
426
427    /// Gets a specific group in the Remote by its id.
428    ///
429    /// If groups have not been pulled, they will be pulled upon calling this.
430    /// This function is only available to accounts with admin status on the Remote.
431    pub fn get_group_by_id(&self, id: GroupId) -> Result<Option<Ref<RemoteGroup>>, ()> {
432        if !self.has_pulled_groups() {
433            self.pull_groups()?;
434        }
435
436        let value = unsafe { BNRemoteGetGroupById(self.handle.as_ptr(), id.0) };
437        Ok(NonNull::new(value).map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) }))
438    }
439
440    /// Gets a specific group in the Remote by its name.
441    ///
442    /// If groups have not been pulled, they will be pulled upon calling this.
443    /// This function is only available to accounts with admin status on the Remote.
444    pub fn get_group_by_name(&self, name: &str) -> Result<Option<Ref<RemoteGroup>>, ()> {
445        if !self.has_pulled_groups() {
446            self.pull_groups()?;
447        }
448
449        let name = name.to_cstr();
450        let value = unsafe { BNRemoteGetGroupByName(self.handle.as_ptr(), name.as_ptr()) };
451
452        Ok(NonNull::new(value).map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) }))
453    }
454
455    /// Searches for groups in the Remote with a given prefix.
456    ///
457    /// # Arguments
458    ///
459    /// * `prefix` - Prefix of name for groups
460    pub fn search_groups(&self, prefix: &str) -> Result<(Array<GroupId>, Array<BnString>), ()> {
461        let prefix = prefix.to_cstr();
462        let mut count = 0;
463        let mut group_ids = std::ptr::null_mut();
464        let mut group_names = std::ptr::null_mut();
465
466        let success = unsafe {
467            BNRemoteSearchGroups(
468                self.handle.as_ptr(),
469                prefix.as_ptr(),
470                &mut group_ids,
471                &mut group_names,
472                &mut count,
473            )
474        };
475        if !success {
476            return Err(());
477        }
478        Ok(unsafe {
479            (
480                Array::new(group_ids, count, ()),
481                Array::new(group_names, count, ()),
482            )
483        })
484    }
485
486    /// Pulls the list of groups from the Remote.
487    /// This function is only available to accounts with admin status on the Remote.
488    pub fn pull_groups(&self) -> Result<(), ()> {
489        self.pull_groups_with_progress(NoProgressCallback)
490    }
491
492    /// Pulls the list of groups from the Remote.
493    /// This function is only available to accounts with admin status on the Remote.
494    ///
495    /// # Arguments
496    ///
497    /// * `progress` - Function to call for progress updates
498    pub fn pull_groups_with_progress<F: ProgressCallback>(
499        &self,
500        mut progress: F,
501    ) -> Result<(), ()> {
502        let success = unsafe {
503            BNRemotePullGroups(
504                self.handle.as_ptr(),
505                Some(F::cb_progress_callback),
506                &mut progress as *mut F as *mut c_void,
507            )
508        };
509        success.then_some(()).ok_or(())
510    }
511
512    /// Creates a new group on the remote (and pull it).
513    /// This function is only available to accounts with admin status on the Remote.
514    ///
515    /// # Arguments
516    ///
517    /// * `name` - Group name
518    /// * `users` - List of users in the group
519    pub fn create_group<I>(&self, name: &str, users: I) -> Result<Ref<RemoteGroup>, ()>
520    where
521        I: IntoIterator<Item = Ref<RemoteUser>>,
522    {
523        let name = name.to_cstr();
524        let mut user_ptrs: Vec<_> = users.into_iter().map(|s| s.handle.as_ptr()).collect();
525
526        let value = unsafe {
527            BNRemoteCreateGroup(
528                self.handle.as_ptr(),
529                name.as_ptr(),
530                user_ptrs.as_mut_ptr(),
531                user_ptrs.len(),
532            )
533        };
534        NonNull::new(value)
535            .map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) })
536            .ok_or(())
537    }
538
539    /// Pushes an updated Group object to the Remote.
540    /// This function is only available to accounts with admin status on the Remote.
541    ///
542    /// # Arguments
543    ///
544    /// * `group` - Group object which has been updated
545    /// * `extra_fields` - Extra HTTP fields to send with the update
546    pub fn push_group<I>(&self, group: &RemoteGroup, extra_fields: I) -> Result<(), ()>
547    where
548        I: IntoIterator<Item = (String, String)>,
549    {
550        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
551            .into_iter()
552            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
553            .unzip();
554        let mut keys_raw: Vec<_> = keys.iter().map(|s| s.as_ptr()).collect();
555        let mut values_raw: Vec<_> = values.iter().map(|s| s.as_ptr()).collect();
556
557        let success = unsafe {
558            BNRemotePushGroup(
559                self.handle.as_ptr(),
560                group.handle.as_ptr(),
561                keys_raw.as_mut_ptr(),
562                values_raw.as_mut_ptr(),
563                keys.len(),
564            )
565        };
566        success.then_some(()).ok_or(())
567    }
568
569    /// Deletes the specified group from the remote.
570    ///
571    /// NOTE: This function is only available to accounts with admin status on the Remote
572    ///
573    /// # Arguments
574    ///
575    /// * `group` - Reference to the group to delete.
576    pub fn delete_group(&self, group: &RemoteGroup) -> Result<(), ()> {
577        let success = unsafe { BNRemoteDeleteGroup(self.handle.as_ptr(), group.handle.as_ptr()) };
578        success.then_some(()).ok_or(())
579    }
580
581    /// Retrieves the list of users in the project.
582    ///
583    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
584    ///
585    /// NOTE: This function is only available to accounts with admin status on the Remote
586    pub fn users(&self) -> Result<Array<RemoteUser>, ()> {
587        if !self.has_pulled_users() {
588            self.pull_users()?;
589        }
590        let mut count = 0;
591        let value = unsafe { BNRemoteGetUsers(self.handle.as_ptr(), &mut count) };
592        if value.is_null() {
593            return Err(());
594        }
595        Ok(unsafe { Array::new(value, count, ()) })
596    }
597
598    /// Retrieves a specific user in the project by their ID.
599    ///
600    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
601    ///
602    /// NOTE: This function is only available to accounts with admin status on the Remote
603    ///
604    /// # Arguments
605    ///
606    /// * `id` - The identifier of the user to retrieve.
607    pub fn get_user_by_id(&self, id: &str) -> Result<Option<Ref<RemoteUser>>, ()> {
608        if !self.has_pulled_users() {
609            self.pull_users()?;
610        }
611        let id = id.to_cstr();
612        let value = unsafe { BNRemoteGetUserById(self.handle.as_ptr(), id.as_ptr()) };
613        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
614    }
615
616    /// Retrieves a specific user in the project by their username.
617    ///
618    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
619    ///
620    /// NOTE: This function is only available to accounts with admin status on the Remote
621    ///
622    /// # Arguments
623    ///
624    /// * `username` - The username of the user to retrieve.
625    pub fn get_user_by_username(&self, username: &str) -> Result<Option<Ref<RemoteUser>>, ()> {
626        if !self.has_pulled_users() {
627            self.pull_users()?;
628        }
629        let username = username.to_cstr();
630        let value = unsafe { BNRemoteGetUserByUsername(self.handle.as_ptr(), username.as_ptr()) };
631        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
632    }
633
634    /// Retrieves the user object for the currently connected user.
635    ///
636    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
637    ///
638    /// NOTE: This function is only available to accounts with admin status on the Remote
639    pub fn current_user(&self) -> Result<Option<Ref<RemoteUser>>, ()> {
640        if !self.has_pulled_users() {
641            self.pull_users()?;
642        }
643        let value = unsafe { BNRemoteGetCurrentUser(self.handle.as_ptr()) };
644        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
645    }
646
647    /// Searches for users in the project with a given prefix.
648    ///
649    /// # Arguments
650    ///
651    /// * `prefix` - The prefix to search for in usernames.
652    pub fn search_users(&self, prefix: &str) -> Result<(Array<BnString>, Array<BnString>), ()> {
653        let prefix = prefix.to_cstr();
654        let mut count = 0;
655        let mut user_ids = std::ptr::null_mut();
656        let mut usernames = std::ptr::null_mut();
657        let success = unsafe {
658            BNRemoteSearchUsers(
659                self.handle.as_ptr(),
660                prefix.as_ptr(),
661                &mut user_ids,
662                &mut usernames,
663                &mut count,
664            )
665        };
666
667        if !success {
668            return Err(());
669        }
670        assert!(!user_ids.is_null());
671        assert!(!usernames.is_null());
672        Ok(unsafe {
673            (
674                Array::new(user_ids, count, ()),
675                Array::new(usernames, count, ()),
676            )
677        })
678    }
679
680    /// Pulls the list of users from the remote.
681    ///
682    /// NOTE: This function is only available to accounts with admin status on the Remote.
683    /// Non-admin accounts attempting to call this function will pull an empty list of users.
684    pub fn pull_users(&self) -> Result<(), ()> {
685        self.pull_users_with_progress(NoProgressCallback)
686    }
687
688    /// Pulls the list of users from the remote.
689    ///
690    /// NOTE: This function is only available to accounts with admin status on the Remote.
691    /// Non-admin accounts attempting to call this function will pull an empty list of users.
692    ///
693    /// # Arguments
694    ///
695    /// * `progress` - Closure called to report progress. Takes current and total progress counts.
696    pub fn pull_users_with_progress<P: ProgressCallback>(&self, mut progress: P) -> Result<(), ()> {
697        let success = unsafe {
698            BNRemotePullUsers(
699                self.handle.as_ptr(),
700                Some(P::cb_progress_callback),
701                &mut progress as *mut P as *mut c_void,
702            )
703        };
704        success.then_some(()).ok_or(())
705    }
706
707    /// Creates a new user on the remote and returns a reference to the created user.
708    ///
709    /// NOTE: This function is only available to accounts with admin status on the Remote
710    ///
711    /// # Arguments
712    ///
713    /// * Various details about the new user to be created.
714    pub fn create_user(
715        &self,
716        username: &str,
717        email: &str,
718        is_active: bool,
719        password: &str,
720        group_ids: &[u64],
721        user_permission_ids: &[u64],
722    ) -> Result<Ref<RemoteUser>, ()> {
723        let username = username.to_cstr();
724        let email = email.to_cstr();
725        let password = password.to_cstr();
726
727        let value = unsafe {
728            BNRemoteCreateUser(
729                self.handle.as_ptr(),
730                username.as_ptr(),
731                email.as_ptr(),
732                is_active,
733                password.as_ptr(),
734                group_ids.as_ptr(),
735                group_ids.len(),
736                user_permission_ids.as_ptr(),
737                user_permission_ids.len(),
738            )
739        };
740        NonNull::new(value)
741            .map(|handle| unsafe { RemoteUser::ref_from_raw(handle) })
742            .ok_or(())
743    }
744
745    /// Pushes updates to the specified user on the remote.
746    ///
747    /// NOTE: This function is only available to accounts with admin status on the Remote
748    ///
749    /// # Arguments
750    ///
751    /// * `user` - Reference to the `RemoteUser` object to push.
752    /// * `extra_fields` - Optional extra fields to send with the update.
753    pub fn push_user<I>(&self, user: &RemoteUser, extra_fields: I) -> Result<(), ()>
754    where
755        I: IntoIterator<Item = (String, String)>,
756    {
757        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
758            .into_iter()
759            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
760            .unzip();
761        let mut keys_raw: Vec<_> = keys.iter().map(|s| s.as_ptr()).collect();
762        let mut values_raw: Vec<_> = values.iter().map(|s| s.as_ptr()).collect();
763        let success = unsafe {
764            BNRemotePushUser(
765                self.handle.as_ptr(),
766                user.handle.as_ptr(),
767                keys_raw.as_mut_ptr(),
768                values_raw.as_mut_ptr(),
769                keys_raw.len(),
770            )
771        };
772        success.then_some(()).ok_or(())
773    }
774
775    // TODO identify the request and ret type of this function, it seems to use a C++ implementation of
776    // HTTP requests, composed mostly of `std:vector`.
777    //pub fn request(&self) {
778    //    unsafe { BNRemoteRequest(self.handle.as_ptr(), todo!(), todo!()) }
779    //}
780}
781
782impl PartialEq for Remote {
783    fn eq(&self, other: &Self) -> bool {
784        // don't pull metadata if we hand't yet
785        if !self.has_loaded_metadata() || other.has_loaded_metadata() {
786            self.address() == other.address()
787        } else if let Some((slf, oth)) = self.unique_id().ok().zip(other.unique_id().ok()) {
788            slf == oth
789        } else {
790            // falback to comparing address
791            self.address() == other.address()
792        }
793    }
794}
795impl Eq for Remote {}
796
797impl ToOwned for Remote {
798    type Owned = Ref<Self>;
799
800    fn to_owned(&self) -> Self::Owned {
801        unsafe { RefCountable::inc_ref(self) }
802    }
803}
804
805unsafe impl RefCountable for Remote {
806    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
807        Ref::new(Self {
808            handle: NonNull::new(BNNewRemoteReference(handle.handle.as_ptr())).unwrap(),
809        })
810    }
811
812    unsafe fn dec_ref(handle: &Self) {
813        BNFreeRemote(handle.handle.as_ptr());
814    }
815}
816
817impl CoreArrayProvider for Remote {
818    type Raw = *mut BNRemote;
819    type Context = ();
820    type Wrapped<'a> = Guard<'a, Self>;
821}
822
823unsafe impl CoreArrayProviderInner for Remote {
824    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
825        BNFreeRemoteList(raw, count)
826    }
827
828    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
829        let raw_ptr = NonNull::new(*raw).unwrap();
830        Guard::new(Self::from_raw(raw_ptr), context)
831    }
832}
833
834#[derive(Debug, Clone, PartialEq, Eq, Hash)]
835pub struct ConnectionOptions {
836    pub username: String,
837    /// Provide this if you want to authenticate with a password.
838    pub password: Option<String>,
839    /// Provide this if you want to authenticate with a token.
840    ///
841    /// If you do not have a token you can use [ConnectionOptions::with_password].
842    pub token: Option<String>,
843}
844
845impl ConnectionOptions {
846    pub fn new_with_token(username: String, token: String) -> Self {
847        Self {
848            username,
849            token: Some(token),
850            password: None,
851        }
852    }
853
854    pub fn new_with_password(username: String, password: String) -> Self {
855        Self {
856            username,
857            token: None,
858            password: Some(password),
859        }
860    }
861
862    pub fn with_token(self, token: String) -> Self {
863        Self {
864            token: Some(token),
865            ..self
866        }
867    }
868
869    pub fn with_password(self, token: String) -> Self {
870        Self {
871            token: Some(token),
872            ..self
873        }
874    }
875
876    pub fn from_enterprise() -> Result<Self, ()> {
877        // TODO: Check if enterprise is initialized and error if not.
878        let username = enterprise::server_username();
879        let token = enterprise::server_token();
880        Ok(Self::new_with_token(username, token))
881    }
882
883    /// Retrieves the [`ConnectionOptions`] for the given address.
884    ///
885    /// NOTE: Uses the secret's provider specified by the setting "enterprise.secretsProvider".
886    pub fn from_secrets_provider(address: &str) -> Result<Self, ()> {
887        let secrets_provider_name = Settings::new().get_string("enterprise.secretsProvider");
888        let provider = CoreSecretsProvider::by_name(&secrets_provider_name).ok_or(())?;
889        let cred_data_str = provider.get_data(address);
890        if cred_data_str.is_empty() {
891            return Err(());
892        }
893        let cred_data: serde_json::Value = serde_json::from_str(&cred_data_str).map_err(|_| ())?;
894        let username = cred_data["username"].as_str().ok_or(())?;
895        let token = cred_data["token"].as_str().ok_or(())?;
896        Ok(Self::new_with_token(
897            username.to_string(),
898            token.to_string(),
899        ))
900    }
901
902    pub fn from_env_variables() -> Result<Self, VarError> {
903        let username = std::env::var("BN_ENTERPRISE_USERNAME")?;
904        let password = std::env::var("BN_ENTERPRISE_PASSWORD")?;
905        Ok(ConnectionOptions::new_with_password(username, password))
906    }
907}