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    /// * `usernames` - List of usernames of users in the group
519    pub fn create_group<I>(&self, name: &str, usernames: I) -> Result<Ref<RemoteGroup>, ()>
520    where
521        I: IntoIterator<Item = String>,
522    {
523        let name = name.to_cstr();
524        let usernames: Vec<_> = usernames.into_iter().map(|s| s.to_cstr()).collect();
525        let mut username_ptrs: Vec<_> = usernames.iter().map(|s| s.as_ptr()).collect();
526
527        let value = unsafe {
528            BNRemoteCreateGroup(
529                self.handle.as_ptr(),
530                name.as_ptr(),
531                username_ptrs.as_mut_ptr(),
532                username_ptrs.len(),
533            )
534        };
535        NonNull::new(value)
536            .map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) })
537            .ok_or(())
538    }
539
540    /// Pushes an updated Group object to the Remote.
541    /// This function is only available to accounts with admin status on the Remote.
542    ///
543    /// # Arguments
544    ///
545    /// * `group` - Group object which has been updated
546    /// * `extra_fields` - Extra HTTP fields to send with the update
547    pub fn push_group<I>(&self, group: &RemoteGroup, extra_fields: I) -> Result<(), ()>
548    where
549        I: IntoIterator<Item = (String, String)>,
550    {
551        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
552            .into_iter()
553            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
554            .unzip();
555        let mut keys_raw: Vec<_> = keys.iter().map(|s| s.as_ptr()).collect();
556        let mut values_raw: Vec<_> = values.iter().map(|s| s.as_ptr()).collect();
557
558        let success = unsafe {
559            BNRemotePushGroup(
560                self.handle.as_ptr(),
561                group.handle.as_ptr(),
562                keys_raw.as_mut_ptr(),
563                values_raw.as_mut_ptr(),
564                keys.len(),
565            )
566        };
567        success.then_some(()).ok_or(())
568    }
569
570    /// Deletes the specified group from the remote.
571    ///
572    /// NOTE: This function is only available to accounts with admin status on the Remote
573    ///
574    /// # Arguments
575    ///
576    /// * `group` - Reference to the group to delete.
577    pub fn delete_group(&self, group: &RemoteGroup) -> Result<(), ()> {
578        let success = unsafe { BNRemoteDeleteGroup(self.handle.as_ptr(), group.handle.as_ptr()) };
579        success.then_some(()).ok_or(())
580    }
581
582    /// Retrieves the list of users in the project.
583    ///
584    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
585    ///
586    /// NOTE: This function is only available to accounts with admin status on the Remote
587    pub fn users(&self) -> Result<Array<RemoteUser>, ()> {
588        if !self.has_pulled_users() {
589            self.pull_users()?;
590        }
591        let mut count = 0;
592        let value = unsafe { BNRemoteGetUsers(self.handle.as_ptr(), &mut count) };
593        if value.is_null() {
594            return Err(());
595        }
596        Ok(unsafe { Array::new(value, count, ()) })
597    }
598
599    /// Retrieves a specific user in the project by their ID.
600    ///
601    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
602    ///
603    /// NOTE: This function is only available to accounts with admin status on the Remote
604    ///
605    /// # Arguments
606    ///
607    /// * `id` - The identifier of the user to retrieve.
608    pub fn get_user_by_id(&self, id: &str) -> Result<Option<Ref<RemoteUser>>, ()> {
609        if !self.has_pulled_users() {
610            self.pull_users()?;
611        }
612        let id = id.to_cstr();
613        let value = unsafe { BNRemoteGetUserById(self.handle.as_ptr(), id.as_ptr()) };
614        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
615    }
616
617    /// Retrieves a specific user in the project by their username.
618    ///
619    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
620    ///
621    /// NOTE: This function is only available to accounts with admin status on the Remote
622    ///
623    /// # Arguments
624    ///
625    /// * `username` - The username of the user to retrieve.
626    pub fn get_user_by_username(&self, username: &str) -> Result<Option<Ref<RemoteUser>>, ()> {
627        if !self.has_pulled_users() {
628            self.pull_users()?;
629        }
630        let username = username.to_cstr();
631        let value = unsafe { BNRemoteGetUserByUsername(self.handle.as_ptr(), username.as_ptr()) };
632        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
633    }
634
635    /// Retrieves the user object for the currently connected user.
636    ///
637    /// NOTE: If users have not been pulled, they will be pulled upon calling this.
638    ///
639    /// NOTE: This function is only available to accounts with admin status on the Remote
640    pub fn current_user(&self) -> Result<Option<Ref<RemoteUser>>, ()> {
641        if !self.has_pulled_users() {
642            self.pull_users()?;
643        }
644        let value = unsafe { BNRemoteGetCurrentUser(self.handle.as_ptr()) };
645        Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
646    }
647
648    /// Searches for users in the project with a given prefix.
649    ///
650    /// # Arguments
651    ///
652    /// * `prefix` - The prefix to search for in usernames.
653    pub fn search_users(&self, prefix: &str) -> Result<(Array<BnString>, Array<BnString>), ()> {
654        let prefix = prefix.to_cstr();
655        let mut count = 0;
656        let mut user_ids = std::ptr::null_mut();
657        let mut usernames = std::ptr::null_mut();
658        let success = unsafe {
659            BNRemoteSearchUsers(
660                self.handle.as_ptr(),
661                prefix.as_ptr(),
662                &mut user_ids,
663                &mut usernames,
664                &mut count,
665            )
666        };
667
668        if !success {
669            return Err(());
670        }
671        assert!(!user_ids.is_null());
672        assert!(!usernames.is_null());
673        Ok(unsafe {
674            (
675                Array::new(user_ids, count, ()),
676                Array::new(usernames, count, ()),
677            )
678        })
679    }
680
681    /// Pulls the list of users from the remote.
682    ///
683    /// NOTE: This function is only available to accounts with admin status on the Remote.
684    /// Non-admin accounts attempting to call this function will pull an empty list of users.
685    pub fn pull_users(&self) -> Result<(), ()> {
686        self.pull_users_with_progress(NoProgressCallback)
687    }
688
689    /// Pulls the list of users from the remote.
690    ///
691    /// NOTE: This function is only available to accounts with admin status on the Remote.
692    /// Non-admin accounts attempting to call this function will pull an empty list of users.
693    ///
694    /// # Arguments
695    ///
696    /// * `progress` - Closure called to report progress. Takes current and total progress counts.
697    pub fn pull_users_with_progress<P: ProgressCallback>(&self, mut progress: P) -> Result<(), ()> {
698        let success = unsafe {
699            BNRemotePullUsers(
700                self.handle.as_ptr(),
701                Some(P::cb_progress_callback),
702                &mut progress as *mut P as *mut c_void,
703            )
704        };
705        success.then_some(()).ok_or(())
706    }
707
708    /// Creates a new user on the remote and returns a reference to the created user.
709    ///
710    /// NOTE: This function is only available to accounts with admin status on the Remote
711    ///
712    /// # Arguments
713    ///
714    /// * Various details about the new user to be created.
715    pub fn create_user(
716        &self,
717        username: &str,
718        email: &str,
719        is_active: bool,
720        password: &str,
721        group_ids: &[u64],
722        user_permission_ids: &[u64],
723    ) -> Result<Ref<RemoteUser>, ()> {
724        let username = username.to_cstr();
725        let email = email.to_cstr();
726        let password = password.to_cstr();
727
728        let value = unsafe {
729            BNRemoteCreateUser(
730                self.handle.as_ptr(),
731                username.as_ptr(),
732                email.as_ptr(),
733                is_active,
734                password.as_ptr(),
735                group_ids.as_ptr(),
736                group_ids.len(),
737                user_permission_ids.as_ptr(),
738                user_permission_ids.len(),
739            )
740        };
741        NonNull::new(value)
742            .map(|handle| unsafe { RemoteUser::ref_from_raw(handle) })
743            .ok_or(())
744    }
745
746    /// Pushes updates to the specified user on the remote.
747    ///
748    /// NOTE: This function is only available to accounts with admin status on the Remote
749    ///
750    /// # Arguments
751    ///
752    /// * `user` - Reference to the `RemoteUser` object to push.
753    /// * `extra_fields` - Optional extra fields to send with the update.
754    pub fn push_user<I>(&self, user: &RemoteUser, extra_fields: I) -> Result<(), ()>
755    where
756        I: IntoIterator<Item = (String, String)>,
757    {
758        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
759            .into_iter()
760            .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
761            .unzip();
762        let mut keys_raw: Vec<_> = keys.iter().map(|s| s.as_ptr()).collect();
763        let mut values_raw: Vec<_> = values.iter().map(|s| s.as_ptr()).collect();
764        let success = unsafe {
765            BNRemotePushUser(
766                self.handle.as_ptr(),
767                user.handle.as_ptr(),
768                keys_raw.as_mut_ptr(),
769                values_raw.as_mut_ptr(),
770                keys_raw.len(),
771            )
772        };
773        success.then_some(()).ok_or(())
774    }
775
776    // TODO identify the request and ret type of this function, it seems to use a C++ implementation of
777    // HTTP requests, composed mostly of `std:vector`.
778    //pub fn request(&self) {
779    //    unsafe { BNRemoteRequest(self.handle.as_ptr(), todo!(), todo!()) }
780    //}
781}
782
783impl PartialEq for Remote {
784    fn eq(&self, other: &Self) -> bool {
785        // don't pull metadata if we hand't yet
786        if !self.has_loaded_metadata() || other.has_loaded_metadata() {
787            self.address() == other.address()
788        } else if let Some((slf, oth)) = self.unique_id().ok().zip(other.unique_id().ok()) {
789            slf == oth
790        } else {
791            // falback to comparing address
792            self.address() == other.address()
793        }
794    }
795}
796impl Eq for Remote {}
797
798impl ToOwned for Remote {
799    type Owned = Ref<Self>;
800
801    fn to_owned(&self) -> Self::Owned {
802        unsafe { RefCountable::inc_ref(self) }
803    }
804}
805
806unsafe impl RefCountable for Remote {
807    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
808        Ref::new(Self {
809            handle: NonNull::new(BNNewRemoteReference(handle.handle.as_ptr())).unwrap(),
810        })
811    }
812
813    unsafe fn dec_ref(handle: &Self) {
814        BNFreeRemote(handle.handle.as_ptr());
815    }
816}
817
818impl CoreArrayProvider for Remote {
819    type Raw = *mut BNRemote;
820    type Context = ();
821    type Wrapped<'a> = Guard<'a, Self>;
822}
823
824unsafe impl CoreArrayProviderInner for Remote {
825    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
826        BNFreeRemoteList(raw, count)
827    }
828
829    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
830        let raw_ptr = NonNull::new(*raw).unwrap();
831        Guard::new(Self::from_raw(raw_ptr), context)
832    }
833}
834
835#[derive(Debug, Clone, PartialEq, Eq, Hash)]
836pub struct ConnectionOptions {
837    pub username: String,
838    /// Provide this if you want to authenticate with a password.
839    pub password: Option<String>,
840    /// Provide this if you want to authenticate with a token.
841    ///
842    /// If you do not have a token you can use [ConnectionOptions::with_password].
843    pub token: Option<String>,
844}
845
846impl ConnectionOptions {
847    pub fn new_with_token(username: String, token: String) -> Self {
848        Self {
849            username,
850            token: Some(token),
851            password: None,
852        }
853    }
854
855    pub fn new_with_password(username: String, password: String) -> Self {
856        Self {
857            username,
858            token: None,
859            password: Some(password),
860        }
861    }
862
863    pub fn with_token(self, token: String) -> Self {
864        Self {
865            token: Some(token),
866            ..self
867        }
868    }
869
870    pub fn with_password(self, token: String) -> Self {
871        Self {
872            token: Some(token),
873            ..self
874        }
875    }
876
877    pub fn from_enterprise() -> Result<Self, ()> {
878        // TODO: Check if enterprise is initialized and error if not.
879        let username = enterprise::server_username();
880        let token = enterprise::server_token();
881        Ok(Self::new_with_token(username, token))
882    }
883
884    /// Retrieves the [`ConnectionOptions`] for the given address.
885    ///
886    /// NOTE: Uses the secret's provider specified by the setting "enterprise.secretsProvider".
887    pub fn from_secrets_provider(address: &str) -> Result<Self, ()> {
888        let secrets_provider_name = Settings::new().get_string("enterprise.secretsProvider");
889        let provider = CoreSecretsProvider::by_name(&secrets_provider_name).ok_or(())?;
890        let cred_data_str = provider.get_data(address);
891        if cred_data_str.is_empty() {
892            return Err(());
893        }
894        let cred_data: serde_json::Value = serde_json::from_str(&cred_data_str).map_err(|_| ())?;
895        let username = cred_data["username"].as_str().ok_or(())?;
896        let token = cred_data["token"].as_str().ok_or(())?;
897        Ok(Self::new_with_token(
898            username.to_string(),
899            token.to_string(),
900        ))
901    }
902
903    pub fn from_env_variables() -> Result<Self, VarError> {
904        let username = std::env::var("BN_ENTERPRISE_USERNAME")?;
905        let password = std::env::var("BN_ENTERPRISE_PASSWORD")?;
906        Ok(ConnectionOptions::new_with_password(username, password))
907    }
908}