summaryrefslogtreecommitdiff
path: root/rust/src/collaboration/remote.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/collaboration/remote.rs')
-rw-r--r--rust/src/collaboration/remote.rs959
1 files changed, 959 insertions, 0 deletions
diff --git a/rust/src/collaboration/remote.rs b/rust/src/collaboration/remote.rs
new file mode 100644
index 00000000..baf2ce80
--- /dev/null
+++ b/rust/src/collaboration/remote.rs
@@ -0,0 +1,959 @@
+use binaryninjacore_sys::*;
+use std::ffi::{c_char, c_void};
+use std::ptr::NonNull;
+
+use super::{sync, GroupId, RemoteGroup, RemoteProject, RemoteUser};
+
+use crate::binary_view::BinaryView;
+use crate::database::Database;
+use crate::enterprise;
+use crate::progress::{NoProgressCallback, ProgressCallback};
+use crate::project::Project;
+use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
+use crate::string::{BnStrCompatible, BnString};
+
+#[repr(transparent)]
+pub struct Remote {
+ pub(crate) handle: NonNull<BNRemote>,
+}
+
+impl Remote {
+ pub(crate) unsafe fn from_raw(handle: NonNull<BNRemote>) -> Self {
+ Self { handle }
+ }
+
+ pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNRemote>) -> Ref<Self> {
+ Ref::new(Self { handle })
+ }
+
+ /// Create a Remote and add it to the list of known remotes (saved to Settings)
+ pub fn new<N: BnStrCompatible, A: BnStrCompatible>(name: N, address: A) -> Ref<Self> {
+ let name = name.into_bytes_with_nul();
+ let address = address.into_bytes_with_nul();
+ let result = unsafe {
+ BNCollaborationCreateRemote(
+ name.as_ref().as_ptr() as *const c_char,
+ address.as_ref().as_ptr() as *const c_char,
+ )
+ };
+ unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) }
+ }
+
+ /// Get the Remote for a Database
+ pub fn get_for_local_database(database: &Database) -> Result<Option<Ref<Remote>>, ()> {
+ sync::get_remote_for_local_database(database)
+ }
+
+ /// Get the Remote for a Binary View
+ pub fn get_for_binary_view(bv: &BinaryView) -> Result<Option<Ref<Remote>>, ()> {
+ sync::get_remote_for_binary_view(bv)
+ }
+
+ /// Checks if the remote has pulled metadata like its id, etc.
+ pub fn has_loaded_metadata(&self) -> bool {
+ unsafe { BNRemoteHasLoadedMetadata(self.handle.as_ptr()) }
+ }
+
+ /// Gets the unique id. If metadata has not been pulled, it will be pulled upon calling this.
+ pub fn unique_id(&self) -> Result<BnString, ()> {
+ if !self.has_loaded_metadata() {
+ self.load_metadata()?;
+ }
+ let result = unsafe { BNRemoteGetUniqueId(self.handle.as_ptr()) };
+ assert!(!result.is_null());
+ Ok(unsafe { BnString::from_raw(result) })
+ }
+
+ /// Gets the name of the remote.
+ pub fn name(&self) -> BnString {
+ let result = unsafe { BNRemoteGetName(self.handle.as_ptr()) };
+ assert!(!result.is_null());
+ unsafe { BnString::from_raw(result) }
+ }
+
+ /// Gets the address of the remote.
+ pub fn address(&self) -> BnString {
+ let result = unsafe { BNRemoteGetAddress(self.handle.as_ptr()) };
+ assert!(!result.is_null());
+ unsafe { BnString::from_raw(result) }
+ }
+
+ /// Checks if the remote is connected.
+ pub fn is_connected(&self) -> bool {
+ unsafe { BNRemoteIsConnected(self.handle.as_ptr()) }
+ }
+
+ /// Gets the username used to connect to the remote.
+ pub fn username(&self) -> BnString {
+ let result = unsafe { BNRemoteGetUsername(self.handle.as_ptr()) };
+ assert!(!result.is_null());
+ unsafe { BnString::from_raw(result) }
+ }
+
+ /// Gets the token used to connect to the remote.
+ pub fn token(&self) -> BnString {
+ let result = unsafe { BNRemoteGetToken(self.handle.as_ptr()) };
+ assert!(!result.is_null());
+ unsafe { BnString::from_raw(result) }
+ }
+
+ /// Gets the server version. If metadata has not been pulled, it will be pulled upon calling this.
+ pub fn server_version(&self) -> Result<i32, ()> {
+ if !self.has_loaded_metadata() {
+ self.load_metadata()?;
+ }
+ Ok(unsafe { BNRemoteGetServerVersion(self.handle.as_ptr()) })
+ }
+
+ /// Gets the server build id. If metadata has not been pulled, it will be pulled upon calling this.
+ pub fn server_build_id(&self) -> Result<BnString, ()> {
+ if !self.has_loaded_metadata() {
+ self.load_metadata()?;
+ }
+ unsafe {
+ Ok(BnString::from_raw(BNRemoteGetServerBuildId(
+ self.handle.as_ptr(),
+ )))
+ }
+ }
+
+ /// Gets the list of supported authentication backends on the server.
+ /// If metadata has not been pulled, it will be pulled upon calling this.
+ pub fn auth_backends(&self) -> Result<(Array<BnString>, Array<BnString>), ()> {
+ if !self.has_loaded_metadata() {
+ self.load_metadata()?;
+ }
+
+ let mut backend_ids = std::ptr::null_mut();
+ let mut backend_names = std::ptr::null_mut();
+ let mut count = 0;
+ let success = unsafe {
+ BNRemoteGetAuthBackends(
+ self.handle.as_ptr(),
+ &mut backend_ids,
+ &mut backend_names,
+ &mut count,
+ )
+ };
+ success
+ .then(|| unsafe {
+ (
+ Array::new(backend_ids, count, ()),
+ Array::new(backend_names, count, ()),
+ )
+ })
+ .ok_or(())
+ }
+
+ /// Checks if the current user is an administrator.
+ pub fn is_admin(&self) -> Result<bool, ()> {
+ if !self.has_pulled_users() {
+ self.pull_users()?;
+ }
+ Ok(unsafe { BNRemoteIsAdmin(self.handle.as_ptr()) })
+ }
+
+ /// Checks if the remote is the same as the Enterprise License server.
+ pub fn is_enterprise(&self) -> Result<bool, ()> {
+ if !self.has_loaded_metadata() {
+ self.load_metadata()?;
+ }
+ Ok(unsafe { BNRemoteIsEnterprise(self.handle.as_ptr()) })
+ }
+
+ /// Loads metadata from the remote, including unique id and versions.
+ pub fn load_metadata(&self) -> Result<(), ()> {
+ let success = unsafe { BNRemoteLoadMetadata(self.handle.as_ptr()) };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Requests an authentication token using a username and password.
+ pub fn request_authentication_token<U: BnStrCompatible, P: BnStrCompatible>(
+ &self,
+ username: U,
+ password: P,
+ ) -> Option<BnString> {
+ let username = username.into_bytes_with_nul();
+ let password = password.into_bytes_with_nul();
+ let token = unsafe {
+ BNRemoteRequestAuthenticationToken(
+ self.handle.as_ptr(),
+ username.as_ref().as_ptr() as *const c_char,
+ password.as_ref().as_ptr() as *const c_char,
+ )
+ };
+ if token.is_null() {
+ None
+ } else {
+ Some(unsafe { BnString::from_raw(token) })
+ }
+ }
+
+ /// Connects to the Remote, loading metadata and optionally acquiring a token.
+ ///
+ /// Use [Remote::connect_with_opts] if you cannot otherwise automatically connect using enterprise.
+ pub fn connect(&self) -> Result<(), ()> {
+ // TODO: implement SecretsProvider
+ if self.is_enterprise()? && enterprise::is_server_authenticated() {
+ self.connect_with_opts(ConnectionOptions::from_enterprise()?)
+ } else {
+ // TODO: Make this error instead.
+ let username =
+ std::env::var("BN_ENTERPRISE_USERNAME").expect("No username for connection!");
+ let password =
+ std::env::var("BN_ENTERPRISE_PASSWORD").expect("No password for connection!");
+ let connection_opts = ConnectionOptions::new_with_password(username, password);
+ self.connect_with_opts(connection_opts)
+ }
+ }
+
+ // TODO: This needs docs and proper error.
+ pub fn connect_with_opts(&self, options: ConnectionOptions) -> Result<(), ()> {
+ // TODO: Should we make used load metadata first?
+ if !self.has_loaded_metadata() {
+ self.load_metadata()?;
+ }
+ let token = match options.token {
+ Some(token) => token,
+ None => {
+ // TODO: If password not defined than error saying no token or password
+ let password = options
+ .password
+ .expect("No password or token for connection!");
+ let token = self.request_authentication_token(&options.username, password);
+ // TODO: Error if None.
+ token.unwrap().to_string()
+ }
+ };
+ let username = options.username.into_bytes_with_nul();
+ let username_ptr = username.as_ptr() as *const c_char;
+ let token = token.into_bytes_with_nul();
+ let token_ptr = token.as_ptr() as *const c_char;
+ let success = unsafe { BNRemoteConnect(self.handle.as_ptr(), username_ptr, token_ptr) };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Disconnects from the remote.
+ pub fn disconnect(&self) -> Result<(), ()> {
+ let success = unsafe { BNRemoteDisconnect(self.handle.as_ptr()) };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Checks if the project has pulled the projects yet.
+ pub fn has_pulled_projects(&self) -> bool {
+ unsafe { BNRemoteHasPulledProjects(self.handle.as_ptr()) }
+ }
+
+ /// Checks if the project has pulled the groups yet.
+ pub fn has_pulled_groups(&self) -> bool {
+ unsafe { BNRemoteHasPulledGroups(self.handle.as_ptr()) }
+ }
+
+ /// Checks if the project has pulled the users yet.
+ pub fn has_pulled_users(&self) -> bool {
+ unsafe { BNRemoteHasPulledUsers(self.handle.as_ptr()) }
+ }
+
+ /// Gets the list of projects in this project.
+ ///
+ /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
+ pub fn projects(&self) -> Result<Array<RemoteProject>, ()> {
+ if !self.has_pulled_projects() {
+ self.pull_projects()?;
+ }
+
+ let mut count = 0;
+ let value = unsafe { BNRemoteGetProjects(self.handle.as_ptr(), &mut count) };
+ if value.is_null() {
+ return Err(());
+ }
+ Ok(unsafe { Array::new(value, count, ()) })
+ }
+
+ /// Gets a specific project in the Remote by its id.
+ ///
+ /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
+ pub fn get_project_by_id<S: BnStrCompatible>(
+ &self,
+ id: S,
+ ) -> Result<Option<Ref<RemoteProject>>, ()> {
+ if !self.has_pulled_projects() {
+ self.pull_projects()?;
+ }
+
+ let id = id.into_bytes_with_nul();
+ let value = unsafe {
+ BNRemoteGetProjectById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const c_char)
+ };
+ Ok(NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) }))
+ }
+
+ /// Gets a specific project in the Remote by its name.
+ ///
+ /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
+ pub fn get_project_by_name<S: BnStrCompatible>(
+ &self,
+ name: S,
+ ) -> Result<Option<Ref<RemoteProject>>, ()> {
+ if !self.has_pulled_projects() {
+ self.pull_projects()?;
+ }
+
+ let name = name.into_bytes_with_nul();
+ let value = unsafe {
+ BNRemoteGetProjectByName(
+ self.handle.as_ptr(),
+ name.as_ref().as_ptr() as *const c_char,
+ )
+ };
+ Ok(NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) }))
+ }
+
+ /// Pulls the list of projects from the Remote.
+ pub fn pull_projects(&self) -> Result<(), ()> {
+ self.pull_projects_with_progress(NoProgressCallback)
+ }
+
+ /// Pulls the list of projects from the Remote.
+ ///
+ /// # Arguments
+ ///
+ /// * `progress` - Function to call for progress updates
+ pub fn pull_projects_with_progress<F: ProgressCallback>(
+ &self,
+ mut progress: F,
+ ) -> Result<(), ()> {
+ let success = unsafe {
+ BNRemotePullProjects(
+ self.handle.as_ptr(),
+ Some(F::cb_progress_callback),
+ &mut progress as *mut F as *mut c_void,
+ )
+ };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Creates a new project on the remote (and pull it).
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - Project name
+ /// * `description` - Project description
+ pub fn create_project<N: BnStrCompatible, D: BnStrCompatible>(
+ &self,
+ name: N,
+ description: D,
+ ) -> Result<Ref<RemoteProject>, ()> {
+ // TODO: Do we want this?
+ // TODO: If you have not yet pulled projects you will have never filled the map you will be placing your
+ // TODO: New project in.
+ if !self.has_pulled_projects() {
+ self.pull_projects()?;
+ }
+ let name = name.into_bytes_with_nul();
+ let description = description.into_bytes_with_nul();
+ let value = unsafe {
+ BNRemoteCreateProject(
+ self.handle.as_ptr(),
+ name.as_ref().as_ptr() as *const c_char,
+ description.as_ref().as_ptr() as *const c_char,
+ )
+ };
+ NonNull::new(value)
+ .map(|handle| unsafe { RemoteProject::ref_from_raw(handle) })
+ .ok_or(())
+ }
+
+ /// Create a new project on the remote from a local project.
+ pub fn import_local_project(&self, project: &Project) -> Option<Ref<RemoteProject>> {
+ self.import_local_project_with_progress(project, NoProgressCallback)
+ }
+
+ /// Create a new project on the remote from a local project.
+ pub fn import_local_project_with_progress<P: ProgressCallback>(
+ &self,
+ project: &Project,
+ mut progress: P,
+ ) -> Option<Ref<RemoteProject>> {
+ let value = unsafe {
+ BNRemoteImportLocalProject(
+ self.handle.as_ptr(),
+ project.handle.as_ptr(),
+ Some(P::cb_progress_callback),
+ &mut progress as *mut P as *mut c_void,
+ )
+ };
+ NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) })
+ }
+
+ /// Pushes an updated Project object to the Remote.
+ ///
+ /// # Arguments
+ ///
+ /// * `project` - Project object which has been updated
+ /// * `extra_fields` - Extra HTTP fields to send with the update
+ pub fn push_project<I, K, V>(&self, project: &RemoteProject, extra_fields: I) -> Result<(), ()>
+ where
+ I: Iterator<Item = (K, V)>,
+ K: BnStrCompatible,
+ V: BnStrCompatible,
+ {
+ let (keys, values): (Vec<_>, Vec<_>) = extra_fields
+ .into_iter()
+ .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
+ .unzip();
+ let mut keys_raw = keys
+ .iter()
+ .map(|s| s.as_ref().as_ptr() as *const c_char)
+ .collect::<Vec<_>>();
+ let mut values_raw = values
+ .iter()
+ .map(|s| s.as_ref().as_ptr() as *const c_char)
+ .collect::<Vec<_>>();
+
+ let success = unsafe {
+ BNRemotePushProject(
+ self.handle.as_ptr(),
+ project.handle.as_ptr(),
+ keys_raw.as_mut_ptr(),
+ values_raw.as_mut_ptr(),
+ keys_raw.len(),
+ )
+ };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Deletes a project from the remote.
+ pub fn delete_project(&self, project: &RemoteProject) -> Result<(), ()> {
+ let success =
+ unsafe { BNRemoteDeleteProject(self.handle.as_ptr(), project.handle.as_ptr()) };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Gets the list of groups in this project.
+ ///
+ /// If groups have not been pulled, they will be pulled upon calling this.
+ /// This function is only available to accounts with admin status on the Remote.
+ pub fn groups(&self) -> Result<Array<RemoteGroup>, ()> {
+ if !self.has_pulled_groups() {
+ self.pull_groups()?;
+ }
+
+ let mut count = 0;
+ let value = unsafe { BNRemoteGetGroups(self.handle.as_ptr(), &mut count) };
+ if value.is_null() {
+ return Err(());
+ }
+ Ok(unsafe { Array::new(value, count, ()) })
+ }
+
+ /// Gets a specific group in the Remote by its id.
+ ///
+ /// If groups have not been pulled, they will be pulled upon calling this.
+ /// This function is only available to accounts with admin status on the Remote.
+ pub fn get_group_by_id(&self, id: GroupId) -> Result<Option<Ref<RemoteGroup>>, ()> {
+ if !self.has_pulled_groups() {
+ self.pull_groups()?;
+ }
+
+ let value = unsafe { BNRemoteGetGroupById(self.handle.as_ptr(), id.0) };
+ Ok(NonNull::new(value).map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) }))
+ }
+
+ /// Gets a specific group in the Remote by its name.
+ ///
+ /// If groups have not been pulled, they will be pulled upon calling this.
+ /// This function is only available to accounts with admin status on the Remote.
+ pub fn get_group_by_name<S: BnStrCompatible>(
+ &self,
+ name: S,
+ ) -> Result<Option<Ref<RemoteGroup>>, ()> {
+ if !self.has_pulled_groups() {
+ self.pull_groups()?;
+ }
+
+ let name = name.into_bytes_with_nul();
+ let value = unsafe {
+ BNRemoteGetGroupByName(
+ self.handle.as_ptr(),
+ name.as_ref().as_ptr() as *const c_char,
+ )
+ };
+
+ Ok(NonNull::new(value).map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) }))
+ }
+
+ /// Searches for groups in the Remote with a given prefix.
+ ///
+ /// # Arguments
+ ///
+ /// * `prefix` - Prefix of name for groups
+ pub fn search_groups<S: BnStrCompatible>(
+ &self,
+ prefix: S,
+ ) -> Result<(Array<GroupId>, Array<BnString>), ()> {
+ let prefix = prefix.into_bytes_with_nul();
+ let mut count = 0;
+ let mut group_ids = std::ptr::null_mut();
+ let mut group_names = std::ptr::null_mut();
+
+ let success = unsafe {
+ BNRemoteSearchGroups(
+ self.handle.as_ptr(),
+ prefix.as_ref().as_ptr() as *const c_char,
+ &mut group_ids,
+ &mut group_names,
+ &mut count,
+ )
+ };
+ if !success {
+ return Err(());
+ }
+ Ok(unsafe {
+ (
+ Array::new(group_ids, count, ()),
+ Array::new(group_names, count, ()),
+ )
+ })
+ }
+
+ /// Pulls the list of groups from the Remote.
+ /// This function is only available to accounts with admin status on the Remote.
+ pub fn pull_groups(&self) -> Result<(), ()> {
+ self.pull_groups_with_progress(NoProgressCallback)
+ }
+
+ /// Pulls the list of groups from the Remote.
+ /// This function is only available to accounts with admin status on the Remote.
+ ///
+ /// # Arguments
+ ///
+ /// * `progress` - Function to call for progress updates
+ pub fn pull_groups_with_progress<F: ProgressCallback>(
+ &self,
+ mut progress: F,
+ ) -> Result<(), ()> {
+ let success = unsafe {
+ BNRemotePullGroups(
+ self.handle.as_ptr(),
+ Some(F::cb_progress_callback),
+ &mut progress as *mut F as *mut c_void,
+ )
+ };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Creates a new group on the remote (and pull it).
+ /// This function is only available to accounts with admin status on the Remote.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - Group name
+ /// * `usernames` - List of usernames of users in the group
+ pub fn create_group<N, I>(&self, name: N, usernames: I) -> Result<Ref<RemoteGroup>, ()>
+ where
+ N: BnStrCompatible,
+ I: IntoIterator,
+ I::Item: BnStrCompatible,
+ {
+ let name = name.into_bytes_with_nul();
+ let usernames: Vec<_> = usernames
+ .into_iter()
+ .map(|s| s.into_bytes_with_nul())
+ .collect();
+ let mut username_ptrs: Vec<_> = usernames
+ .iter()
+ .map(|s| s.as_ref().as_ptr() as *const c_char)
+ .collect();
+
+ let value = unsafe {
+ BNRemoteCreateGroup(
+ self.handle.as_ptr(),
+ name.as_ref().as_ptr() as *const c_char,
+ username_ptrs.as_mut_ptr(),
+ username_ptrs.len(),
+ )
+ };
+ NonNull::new(value)
+ .map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) })
+ .ok_or(())
+ }
+
+ /// Pushes an updated Group object to the Remote.
+ /// This function is only available to accounts with admin status on the Remote.
+ ///
+ /// # Arguments
+ ///
+ /// * `group` - Group object which has been updated
+ /// * `extra_fields` - Extra HTTP fields to send with the update
+ pub fn push_group<I, K, V>(&self, group: &RemoteGroup, extra_fields: I) -> Result<(), ()>
+ where
+ I: IntoIterator<Item = (K, V)>,
+ K: BnStrCompatible,
+ V: BnStrCompatible,
+ {
+ let (keys, values): (Vec<_>, Vec<_>) = extra_fields
+ .into_iter()
+ .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
+ .unzip();
+ let mut keys_raw: Vec<_> = keys
+ .iter()
+ .map(|s| s.as_ref().as_ptr() as *const c_char)
+ .collect();
+ let mut values_raw: Vec<_> = values
+ .iter()
+ .map(|s| s.as_ref().as_ptr() as *const c_char)
+ .collect();
+
+ let success = unsafe {
+ BNRemotePushGroup(
+ self.handle.as_ptr(),
+ group.handle.as_ptr(),
+ keys_raw.as_mut_ptr(),
+ values_raw.as_mut_ptr(),
+ keys.len(),
+ )
+ };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Deletes the specified group from the remote.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote
+ ///
+ /// # Arguments
+ ///
+ /// * `group` - Reference to the group to delete.
+ pub fn delete_group(&self, group: &RemoteGroup) -> Result<(), ()> {
+ let success = unsafe { BNRemoteDeleteGroup(self.handle.as_ptr(), group.handle.as_ptr()) };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Retrieves the list of users in the project.
+ ///
+ /// NOTE: If users have not been pulled, they will be pulled upon calling this.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote
+ pub fn users(&self) -> Result<Array<RemoteUser>, ()> {
+ if !self.has_pulled_users() {
+ self.pull_users()?;
+ }
+ let mut count = 0;
+ let value = unsafe { BNRemoteGetUsers(self.handle.as_ptr(), &mut count) };
+ if value.is_null() {
+ return Err(());
+ }
+ Ok(unsafe { Array::new(value, count, ()) })
+ }
+
+ /// Retrieves a specific user in the project by their ID.
+ ///
+ /// NOTE: If users have not been pulled, they will be pulled upon calling this.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The identifier of the user to retrieve.
+ pub fn get_user_by_id<S: BnStrCompatible>(&self, id: S) -> Result<Option<Ref<RemoteUser>>, ()> {
+ if !self.has_pulled_users() {
+ self.pull_users()?;
+ }
+ let id = id.into_bytes_with_nul();
+ let value = unsafe {
+ BNRemoteGetUserById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const c_char)
+ };
+ Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
+ }
+
+ /// Retrieves a specific user in the project by their username.
+ ///
+ /// NOTE: If users have not been pulled, they will be pulled upon calling this.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote
+ ///
+ /// # Arguments
+ ///
+ /// * `username` - The username of the user to retrieve.
+ pub fn get_user_by_username<S: BnStrCompatible>(
+ &self,
+ username: S,
+ ) -> Result<Option<Ref<RemoteUser>>, ()> {
+ if !self.has_pulled_users() {
+ self.pull_users()?;
+ }
+ let username = username.into_bytes_with_nul();
+ let value = unsafe {
+ BNRemoteGetUserByUsername(
+ self.handle.as_ptr(),
+ username.as_ref().as_ptr() as *const c_char,
+ )
+ };
+ Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
+ }
+
+ /// Retrieves the user object for the currently connected user.
+ ///
+ /// NOTE: If users have not been pulled, they will be pulled upon calling this.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote
+ pub fn current_user(&self) -> Result<Option<Ref<RemoteUser>>, ()> {
+ if !self.has_pulled_users() {
+ self.pull_users()?;
+ }
+ let value = unsafe { BNRemoteGetCurrentUser(self.handle.as_ptr()) };
+ Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
+ }
+
+ /// Searches for users in the project with a given prefix.
+ ///
+ /// # Arguments
+ ///
+ /// * `prefix` - The prefix to search for in usernames.
+ pub fn search_users<S: BnStrCompatible>(
+ &self,
+ prefix: S,
+ ) -> Result<(Array<BnString>, Array<BnString>), ()> {
+ let prefix = prefix.into_bytes_with_nul();
+ let mut count = 0;
+ let mut user_ids = std::ptr::null_mut();
+ let mut usernames = std::ptr::null_mut();
+ let success = unsafe {
+ BNRemoteSearchUsers(
+ self.handle.as_ptr(),
+ prefix.as_ref().as_ptr() as *const c_char,
+ &mut user_ids,
+ &mut usernames,
+ &mut count,
+ )
+ };
+
+ if !success {
+ return Err(());
+ }
+ assert!(!user_ids.is_null());
+ assert!(!usernames.is_null());
+ Ok(unsafe {
+ (
+ Array::new(user_ids, count, ()),
+ Array::new(usernames, count, ()),
+ )
+ })
+ }
+
+ /// Pulls the list of users from the remote.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote.
+ /// Non-admin accounts attempting to call this function will pull an empty list of users.
+ pub fn pull_users(&self) -> Result<(), ()> {
+ self.pull_users_with_progress(NoProgressCallback)
+ }
+
+ /// Pulls the list of users from the remote.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote.
+ /// Non-admin accounts attempting to call this function will pull an empty list of users.
+ ///
+ /// # Arguments
+ ///
+ /// * `progress` - Closure called to report progress. Takes current and total progress counts.
+ pub fn pull_users_with_progress<P: ProgressCallback>(&self, mut progress: P) -> Result<(), ()> {
+ let success = unsafe {
+ BNRemotePullUsers(
+ self.handle.as_ptr(),
+ Some(P::cb_progress_callback),
+ &mut progress as *mut P as *mut c_void,
+ )
+ };
+ success.then_some(()).ok_or(())
+ }
+
+ /// Creates a new user on the remote and returns a reference to the created user.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote
+ ///
+ /// # Arguments
+ ///
+ /// * Various details about the new user to be created.
+ pub fn create_user<U: BnStrCompatible, E: BnStrCompatible, P: BnStrCompatible>(
+ &self,
+ username: U,
+ email: E,
+ is_active: bool,
+ password: P,
+ group_ids: &[u64],
+ user_permission_ids: &[u64],
+ ) -> Result<Ref<RemoteUser>, ()> {
+ let username = username.into_bytes_with_nul();
+ let email = email.into_bytes_with_nul();
+ let password = password.into_bytes_with_nul();
+
+ let value = unsafe {
+ BNRemoteCreateUser(
+ self.handle.as_ptr(),
+ username.as_ref().as_ptr() as *const c_char,
+ email.as_ref().as_ptr() as *const c_char,
+ is_active,
+ password.as_ref().as_ptr() as *const c_char,
+ group_ids.as_ptr(),
+ group_ids.len(),
+ user_permission_ids.as_ptr(),
+ user_permission_ids.len(),
+ )
+ };
+ NonNull::new(value)
+ .map(|handle| unsafe { RemoteUser::ref_from_raw(handle) })
+ .ok_or(())
+ }
+
+ /// Pushes updates to the specified user on the remote.
+ ///
+ /// NOTE: This function is only available to accounts with admin status on the Remote
+ ///
+ /// # Arguments
+ ///
+ /// * `user` - Reference to the `RemoteUser` object to push.
+ /// * `extra_fields` - Optional extra fields to send with the update.
+ pub fn push_user<I, K, V>(&self, user: &RemoteUser, extra_fields: I) -> Result<(), ()>
+ where
+ I: Iterator<Item = (K, V)>,
+ K: BnStrCompatible,
+ V: BnStrCompatible,
+ {
+ let (keys, values): (Vec<_>, Vec<_>) = extra_fields
+ .into_iter()
+ .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
+ .unzip();
+ let mut keys_raw: Vec<_> = keys
+ .iter()
+ .map(|s| s.as_ref().as_ptr() as *const c_char)
+ .collect();
+ let mut values_raw: Vec<_> = values
+ .iter()
+ .map(|s| s.as_ref().as_ptr() as *const c_char)
+ .collect();
+ let success = unsafe {
+ BNRemotePushUser(
+ self.handle.as_ptr(),
+ user.handle.as_ptr(),
+ keys_raw.as_mut_ptr(),
+ values_raw.as_mut_ptr(),
+ keys_raw.len(),
+ )
+ };
+ success.then_some(()).ok_or(())
+ }
+
+ // TODO identify the request and ret type of this function, it seems to use a C++ implementation of
+ // HTTP requests, composed mostly of `std:vector`.
+ //pub fn request(&self) {
+ // unsafe { BNRemoteRequest(self.handle.as_ptr(), todo!(), todo!()) }
+ //}
+}
+
+impl PartialEq for Remote {
+ fn eq(&self, other: &Self) -> bool {
+ // don't pull metadata if we hand't yet
+ if !self.has_loaded_metadata() || other.has_loaded_metadata() {
+ self.address() == other.address()
+ } else if let Some((slf, oth)) = self.unique_id().ok().zip(other.unique_id().ok()) {
+ slf == oth
+ } else {
+ // falback to comparing address
+ self.address() == other.address()
+ }
+ }
+}
+impl Eq for Remote {}
+
+impl ToOwned for Remote {
+ type Owned = Ref<Self>;
+
+ fn to_owned(&self) -> Self::Owned {
+ unsafe { RefCountable::inc_ref(self) }
+ }
+}
+
+unsafe impl RefCountable for Remote {
+ unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
+ Ref::new(Self {
+ handle: NonNull::new(BNNewRemoteReference(handle.handle.as_ptr())).unwrap(),
+ })
+ }
+
+ unsafe fn dec_ref(handle: &Self) {
+ BNFreeRemote(handle.handle.as_ptr());
+ }
+}
+
+impl CoreArrayProvider for Remote {
+ type Raw = *mut BNRemote;
+ type Context = ();
+ type Wrapped<'a> = Guard<'a, Self>;
+}
+
+unsafe impl CoreArrayProviderInner for Remote {
+ unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
+ BNFreeRemoteList(raw, count)
+ }
+
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
+ let raw_ptr = NonNull::new(*raw).unwrap();
+ Guard::new(Self::from_raw(raw_ptr), context)
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ConnectionOptions {
+ pub username: String,
+ /// Provide this if you want to authenticate with a password.
+ pub password: Option<String>,
+ /// Provide this if you want to authenticate with a token.
+ ///
+ /// If you do not have a token you can use [ConnectionOptions::self].
+ pub token: Option<String>,
+}
+
+impl ConnectionOptions {
+ pub fn new_with_token(username: String, token: String) -> Self {
+ Self {
+ username,
+ token: Some(token),
+ password: None,
+ }
+ }
+
+ pub fn new_with_password(username: String, password: String) -> Self {
+ Self {
+ username,
+ token: None,
+ password: Some(password),
+ }
+ }
+
+ pub fn with_token(self, token: String) -> Self {
+ Self {
+ token: Some(token),
+ ..self
+ }
+ }
+
+ pub fn with_password(self, token: String) -> Self {
+ Self {
+ token: Some(token),
+ ..self
+ }
+ }
+
+ pub fn from_enterprise() -> Result<Self, ()> {
+ // TODO: Check if enterprise is initialized and error if not.
+ let username = enterprise::server_username();
+ let token = enterprise::server_token();
+ Ok(Self::new_with_token(
+ username.to_string(),
+ token.to_string(),
+ ))
+ }
+
+ // TODO: from_secrets_provider
+}