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        let success = unsafe {
316            BNRemotePullProjects(
317                self.handle.as_ptr(),
318                Some(F::cb_progress_callback),
319                &mut progress as *mut F as *mut c_void,
320            )
321        };
322        success.then_some(()).ok_or(())
323    }
324
325    /// Creates a new project on the remote (and pull it).
326    ///
327    /// # Arguments
328    ///
329    /// * `name` - Project name
330    /// * `description` - Project description
331    pub fn create_project(&self, name: &str, description: &str) -> Result<Ref<RemoteProject>, ()> {
332        // TODO: Do we want this?
333        // TODO: If you have not yet pulled projects you will have never filled the map you will be placing your
334        // TODO: New project in.
335        if !self.has_pulled_projects() {
336            self.pull_projects()?;
337        }
338        let name = name.to_cstr();
339        let description = description.to_cstr();
340        let value = unsafe {
341            BNRemoteCreateProject(self.handle.as_ptr(), name.as_ptr(), description.as_ptr())
342        };
343        NonNull::new(value)
344            .map(|handle| unsafe { RemoteProject::ref_from_raw(handle) })
345            .ok_or(())
346    }
347
348    /// Create a new project on the remote from a local project.
349    pub fn import_local_project(&self, project: &Project) -> Option<Ref<RemoteProject>> {
350        self.import_local_project_with_progress(project, NoProgressCallback)
351    }
352
353    /// Create a new project on the remote from a local project.
354    pub fn import_local_project_with_progress<P: ProgressCallback>(
355        &self,
356        project: &Project,
357        mut progress: P,
358    ) -> Option<Ref<RemoteProject>> {
359        let value = unsafe {
360            BNRemoteImportLocalProject(
361                self.handle.as_ptr(),
362                project.handle.as_ptr(),
363                Some(P::cb_progress_callback),
364                &mut progress as *mut P as *mut c_void,
365            )
366        };
367        NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) })
368    }
369
370    /// Pushes an updated Project object to the Remote.
371    ///
372    /// # Arguments
373    ///
374    /// * `project` - Project object which has been updated
375    /// * `extra_fields` - Extra HTTP fields to send with the update
376    pub fn push_project<I>(&self, project: &RemoteProject, extra_fields: I) -> Result<(), ()>
377    where
378        I: IntoIterator<Item = (String, String)>,
379    {
380        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
381            .into_iter()
382            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
383            .unzip();
384        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
385        let mut values_raw = values.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
386
387        let success = unsafe {
388            BNRemotePushProject(
389                self.handle.as_ptr(),
390                project.handle.as_ptr(),
391                keys_raw.as_mut_ptr(),
392                values_raw.as_mut_ptr(),
393                keys_raw.len(),
394            )
395        };
396        success.then_some(()).ok_or(())
397    }
398
399    /// Deletes a project from the remote.
400    pub fn delete_project(&self, project: &RemoteProject) -> Result<(), ()> {
401        let success =
402            unsafe { BNRemoteDeleteProject(self.handle.as_ptr(), project.handle.as_ptr()) };
403        success.then_some(()).ok_or(())
404    }
405
406    /// Gets the list of groups in this project.
407    ///
408    /// If groups have not been pulled, they will be pulled upon calling this.
409    /// This function is only available to accounts with admin status on the Remote.
410    pub fn groups(&self) -> Result<Array<RemoteGroup>, ()> {
411        if !self.has_pulled_groups() {
412            self.pull_groups()?;
413        }
414
415        let mut count = 0;
416        let value = unsafe { BNRemoteGetGroups(self.handle.as_ptr(), &mut count) };
417        if value.is_null() {
418            return Err(());
419        }
420        Ok(unsafe { Array::new(value, count, ()) })
421    }
422
423    /// Gets a specific group in the Remote by its id.
424    ///
425    /// If groups have not been pulled, they will be pulled upon calling this.
426    /// This function is only available to accounts with admin status on the Remote.
427    pub fn get_group_by_id(&self, id: GroupId) -> Result<Option<Ref<RemoteGroup>>, ()> {
428        if !self.has_pulled_groups() {
429            self.pull_groups()?;
430        }
431
432        let value = unsafe { BNRemoteGetGroupById(self.handle.as_ptr(), id.0) };
433        Ok(NonNull::new(value).map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) }))
434    }
435
436    /// Gets a specific group in the Remote by its name.
437    ///
438    /// If groups have not been pulled, they will be pulled upon calling this.
439    /// This function is only available to accounts with admin status on the Remote.
440    pub fn get_group_by_name(&self, name: &str) -> Result<Option<Ref<RemoteGroup>>, ()> {
441        if !self.has_pulled_groups() {
442            self.pull_groups()?;
443        }
444
445        let name = name.to_cstr();
446        let value = unsafe { BNRemoteGetGroupByName(self.handle.as_ptr(), name.as_ptr()) };
447
448        Ok(NonNull::new(value).map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) }))
449    }
450
451    /// Searches for groups in the Remote with a given prefix.
452    ///
453    /// # Arguments
454    ///
455    /// * `prefix` - Prefix of name for groups
456    pub fn search_groups(&self, prefix: &str) -> Result<(Array<GroupId>, Array<BnString>), ()> {
457        let prefix = prefix.to_cstr();
458        let mut count = 0;
459        let mut group_ids = std::ptr::null_mut();
460        let mut group_names = std::ptr::null_mut();
461
462        let success = unsafe {
463            BNRemoteSearchGroups(
464                self.handle.as_ptr(),
465                prefix.as_ptr(),
466                &mut group_ids,
467                &mut group_names,
468                &mut count,
469            )
470        };
471        if !success {
472            return Err(());
473        }
474        Ok(unsafe {
475            (
476                Array::new(group_ids, count, ()),
477                Array::new(group_names, count, ()),
478            )
479        })
480    }
481
482    /// Pulls the list of groups from the Remote.
483    /// This function is only available to accounts with admin status on the Remote.
484    pub fn pull_groups(&self) -> Result<(), ()> {
485        self.pull_groups_with_progress(NoProgressCallback)
486    }
487
488    /// Pulls the list of groups from the Remote.
489    /// This function is only available to accounts with admin status on the Remote.
490    ///
491    /// # Arguments
492    ///
493    /// * `progress` - Function to call for progress updates
494    pub fn pull_groups_with_progress<F: ProgressCallback>(
495        &self,
496        mut progress: F,
497    ) -> Result<(), ()> {
498        let success = unsafe {
499            BNRemotePullGroups(
500                self.handle.as_ptr(),
501                Some(F::cb_progress_callback),
502                &mut progress as *mut F as *mut c_void,
503            )
504        };
505        success.then_some(()).ok_or(())
506    }
507
508    /// Creates a new group on the remote (and pull it).
509    /// This function is only available to accounts with admin status on the Remote.
510    ///
511    /// # Arguments
512    ///
513    /// * `name` - Group name
514    /// * `usernames` - List of usernames of users in the group
515    pub fn create_group<I>(&self, name: &str, usernames: I) -> Result<Ref<RemoteGroup>, ()>
516    where
517        I: IntoIterator<Item = String>,
518    {
519        let name = name.to_cstr();
520        let usernames: Vec<_> = usernames.into_iter().map(|s| s.to_cstr()).collect();
521        let mut username_ptrs: Vec<_> = usernames.iter().map(|s| s.as_ptr()).collect();
522
523        let value = unsafe {
524            BNRemoteCreateGroup(
525                self.handle.as_ptr(),
526                name.as_ptr(),
527                username_ptrs.as_mut_ptr(),
528                username_ptrs.len(),
529            )
530        };
531        NonNull::new(value)
532            .map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) })
533            .ok_or(())
534    }
535
536    /// Pushes an updated Group object to the Remote.
537    /// This function is only available to accounts with admin status on the Remote.
538    ///
539    /// # Arguments
540    ///
541    /// * `group` - Group object which has been updated
542    /// * `extra_fields` - Extra HTTP fields to send with the update
543    pub fn push_group<I>(&self, group: &RemoteGroup, extra_fields: I) -> Result<(), ()>
544    where
545        I: IntoIterator<Item = (String, String)>,
546    {
547        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
548            .into_iter()
549            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
550            .unzip();
551        let mut keys_raw: Vec<_> = keys.iter().map(|s| s.as_ptr()).collect();
552        let mut values_raw: Vec<_> = values.iter().map(|s| s.as_ptr()).collect();
553
554        let success = unsafe {
555            BNRemotePushGroup(
556                self.handle.as_ptr(),
557                group.handle.as_ptr(),
558                keys_raw.as_mut_ptr(),
559                values_raw.as_mut_ptr(),
560                keys.len(),
561            )
562        };
563        success.then_some(()).ok_or(())
564    }
565
566    /// Deletes the specified group from the remote.
567    ///
568    /// NOTE: This function is only available to accounts with admin status on the Remote
569    ///
570    /// # Arguments
571    ///
572    /// * `group` - Reference to the group to delete.
573    pub fn delete_group(&self, group: &RemoteGroup) -> Result<(), ()> {
574        let success = unsafe { BNRemoteDeleteGroup(self.handle.as_ptr(), group.handle.as_ptr()) };
575        success.then_some(()).ok_or(())
576    }
577
578    /// Retrieves the list of users in the project.
579    ///
580    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
581    ///
582    /// NOTE: This function is only available to accounts with admin status on the Remote
583    pub fn users(&self) -> Result<Array<RemoteUser>, ()> {
584        if !self.has_pulled_users() {
585            self.pull_users()?;
586        }
587        let mut count = 0;
588        let value = unsafe { BNRemoteGetUsers(self.handle.as_ptr(), &mut count) };
589        if value.is_null() {
590            return Err(());
591        }
592        Ok(unsafe { Array::new(value, count, ()) })
593    }
594
595    /// Retrieves a specific user in the project by their ID.
596    ///
597    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
598    ///
599    /// NOTE: This function is only available to accounts with admin status on the Remote
600    ///
601    /// # Arguments
602    ///
603    /// * `id` - The identifier of the user to retrieve.
604    pub fn get_user_by_id(&self, id: &str) -> Result<Option<Ref<RemoteUser>>, ()> {
605        if !self.has_pulled_users() {
606            self.pull_users()?;
607        }
608        let id = id.to_cstr();
609        let value = unsafe { BNRemoteGetUserById(self.handle.as_ptr(), id.as_ptr()) };
610        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
611    }
612
613    /// Retrieves a specific user in the project by their username.
614    ///
615    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
616    ///
617    /// NOTE: This function is only available to accounts with admin status on the Remote
618    ///
619    /// # Arguments
620    ///
621    /// * `username` - The username of the user to retrieve.
622    pub fn get_user_by_username(&self, username: &str) -> Result<Option<Ref<RemoteUser>>, ()> {
623        if !self.has_pulled_users() {
624            self.pull_users()?;
625        }
626        let username = username.to_cstr();
627        let value = unsafe { BNRemoteGetUserByUsername(self.handle.as_ptr(), username.as_ptr()) };
628        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
629    }
630
631    /// Retrieves the user object for the currently connected user.
632    ///
633    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
634    ///
635    /// NOTE: This function is only available to accounts with admin status on the Remote
636    pub fn current_user(&self) -> Result<Option<Ref<RemoteUser>>, ()> {
637        if !self.has_pulled_users() {
638            self.pull_users()?;
639        }
640        let value = unsafe { BNRemoteGetCurrentUser(self.handle.as_ptr()) };
641        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
642    }
643
644    /// Searches for users in the project with a given prefix.
645    ///
646    /// # Arguments
647    ///
648    /// * `prefix` - The prefix to search for in usernames.
649    pub fn search_users(&self, prefix: &str) -> Result<(Array<BnString>, Array<BnString>), ()> {
650        let prefix = prefix.to_cstr();
651        let mut count = 0;
652        let mut user_ids = std::ptr::null_mut();
653        let mut usernames = std::ptr::null_mut();
654        let success = unsafe {
655            BNRemoteSearchUsers(
656                self.handle.as_ptr(),
657                prefix.as_ptr(),
658                &mut user_ids,
659                &mut usernames,
660                &mut count,
661            )
662        };
663
664        if !success {
665            return Err(());
666        }
667        assert!(!user_ids.is_null());
668        assert!(!usernames.is_null());
669        Ok(unsafe {
670            (
671                Array::new(user_ids, count, ()),
672                Array::new(usernames, count, ()),
673            )
674        })
675    }
676
677    /// Pulls the list of users from the remote.
678    ///
679    /// NOTE: This function is only available to accounts with admin status on the Remote.
680    /// Non-admin accounts attempting to call this function will pull an empty list of users.
681    pub fn pull_users(&self) -> Result<(), ()> {
682        self.pull_users_with_progress(NoProgressCallback)
683    }
684
685    /// Pulls the list of users from the remote.
686    ///
687    /// NOTE: This function is only available to accounts with admin status on the Remote.
688    /// Non-admin accounts attempting to call this function will pull an empty list of users.
689    ///
690    /// # Arguments
691    ///
692    /// * `progress` - Closure called to report progress. Takes current and total progress counts.
693    pub fn pull_users_with_progress<P: ProgressCallback>(&self, mut progress: P) -> Result<(), ()> {
694        let success = unsafe {
695            BNRemotePullUsers(
696                self.handle.as_ptr(),
697                Some(P::cb_progress_callback),
698                &mut progress as *mut P as *mut c_void,
699            )
700        };
701        success.then_some(()).ok_or(())
702    }
703
704    /// Creates a new user on the remote and returns a reference to the created user.
705    ///
706    /// NOTE: This function is only available to accounts with admin status on the Remote
707    ///
708    /// # Arguments
709    ///
710    /// * Various details about the new user to be created.
711    pub fn create_user(
712        &self,
713        username: &str,
714        email: &str,
715        is_active: bool,
716        password: &str,
717        group_ids: &[u64],
718        user_permission_ids: &[u64],
719    ) -> Result<Ref<RemoteUser>, ()> {
720        let username = username.to_cstr();
721        let email = email.to_cstr();
722        let password = password.to_cstr();
723
724        let value = unsafe {
725            BNRemoteCreateUser(
726                self.handle.as_ptr(),
727                username.as_ptr(),
728                email.as_ptr(),
729                is_active,
730                password.as_ptr(),
731                group_ids.as_ptr(),
732                group_ids.len(),
733                user_permission_ids.as_ptr(),
734                user_permission_ids.len(),
735            )
736        };
737        NonNull::new(value)
738            .map(|handle| unsafe { RemoteUser::ref_from_raw(handle) })
739            .ok_or(())
740    }
741
742    /// Pushes updates to the specified user on the remote.
743    ///
744    /// NOTE: This function is only available to accounts with admin status on the Remote
745    ///
746    /// # Arguments
747    ///
748    /// * `user` - Reference to the `RemoteUser` object to push.
749    /// * `extra_fields` - Optional extra fields to send with the update.
750    pub fn push_user<I>(&self, user: &RemoteUser, extra_fields: I) -> Result<(), ()>
751    where
752        I: IntoIterator<Item = (String, String)>,
753    {
754        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
755            .into_iter()
756            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
757            .unzip();
758        let mut keys_raw: Vec<_> = keys.iter().map(|s| s.as_ptr()).collect();
759        let mut values_raw: Vec<_> = values.iter().map(|s| s.as_ptr()).collect();
760        let success = unsafe {
761            BNRemotePushUser(
762                self.handle.as_ptr(),
763                user.handle.as_ptr(),
764                keys_raw.as_mut_ptr(),
765                values_raw.as_mut_ptr(),
766                keys_raw.len(),
767            )
768        };
769        success.then_some(()).ok_or(())
770    }
771
772    // TODO identify the request and ret type of this function, it seems to use a C++ implementation of
773    // HTTP requests, composed mostly of `std:vector`.
774    //pub fn request(&self) {
775    //    unsafe { BNRemoteRequest(self.handle.as_ptr(), todo!(), todo!()) }
776    //}
777}
778
779impl PartialEq for Remote {
780    fn eq(&self, other: &Self) -> bool {
781        // don't pull metadata if we hand't yet
782        if !self.has_loaded_metadata() || other.has_loaded_metadata() {
783            self.address() == other.address()
784        } else if let Some((slf, oth)) = self.unique_id().ok().zip(other.unique_id().ok()) {
785            slf == oth
786        } else {
787            // falback to comparing address
788            self.address() == other.address()
789        }
790    }
791}
792impl Eq for Remote {}
793
794impl ToOwned for Remote {
795    type Owned = Ref<Self>;
796
797    fn to_owned(&self) -> Self::Owned {
798        unsafe { RefCountable::inc_ref(self) }
799    }
800}
801
802unsafe impl RefCountable for Remote {
803    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
804        Ref::new(Self {
805            handle: NonNull::new(BNNewRemoteReference(handle.handle.as_ptr())).unwrap(),
806        })
807    }
808
809    unsafe fn dec_ref(handle: &Self) {
810        BNFreeRemote(handle.handle.as_ptr());
811    }
812}
813
814impl CoreArrayProvider for Remote {
815    type Raw = *mut BNRemote;
816    type Context = ();
817    type Wrapped<'a> = Guard<'a, Self>;
818}
819
820unsafe impl CoreArrayProviderInner for Remote {
821    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
822        BNFreeRemoteList(raw, count)
823    }
824
825    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
826        let raw_ptr = NonNull::new(*raw).unwrap();
827        Guard::new(Self::from_raw(raw_ptr), context)
828    }
829}
830
831#[derive(Debug, Clone, PartialEq, Eq, Hash)]
832pub struct ConnectionOptions {
833    pub username: String,
834    /// Provide this if you want to authenticate with a password.
835    pub password: Option<String>,
836    /// Provide this if you want to authenticate with a token.
837    ///
838    /// If you do not have a token you can use [ConnectionOptions::with_password].
839    pub token: Option<String>,
840}
841
842impl ConnectionOptions {
843    pub fn new_with_token(username: String, token: String) -> Self {
844        Self {
845            username,
846            token: Some(token),
847            password: None,
848        }
849    }
850
851    pub fn new_with_password(username: String, password: String) -> Self {
852        Self {
853            username,
854            token: None,
855            password: Some(password),
856        }
857    }
858
859    pub fn with_token(self, token: String) -> Self {
860        Self {
861            token: Some(token),
862            ..self
863        }
864    }
865
866    pub fn with_password(self, token: String) -> Self {
867        Self {
868            token: Some(token),
869            ..self
870        }
871    }
872
873    pub fn from_enterprise() -> Result<Self, ()> {
874        // TODO: Check if enterprise is initialized and error if not.
875        let username = enterprise::server_username();
876        let token = enterprise::server_token();
877        Ok(Self::new_with_token(username, token))
878    }
879
880    /// Retrieves the [`ConnectionOptions`] for the given address.
881    ///
882    /// NOTE: Uses the secret's provider specified by the setting "enterprise.secretsProvider".
883    pub fn from_secrets_provider(address: &str) -> Result<Self, ()> {
884        let secrets_provider_name = Settings::new().get_string("enterprise.secretsProvider");
885        let provider = CoreSecretsProvider::by_name(&secrets_provider_name).ok_or(())?;
886        let cred_data_str = provider.get_data(address);
887        if cred_data_str.is_empty() {
888            return Err(());
889        }
890        let cred_data: serde_json::Value = serde_json::from_str(&cred_data_str).map_err(|_| ())?;
891        let username = cred_data["username"].as_str().ok_or(())?;
892        let token = cred_data["token"].as_str().ok_or(())?;
893        Ok(Self::new_with_token(
894            username.to_string(),
895            token.to_string(),
896        ))
897    }
898
899    pub fn from_env_variables() -> Result<Self, VarError> {
900        let username = std::env::var("BN_ENTERPRISE_USERNAME")?;
901        let password = std::env::var("BN_ENTERPRISE_PASSWORD")?;
902        Ok(ConnectionOptions::new_with_password(username, password))
903    }
904}