#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#![doc = include_str!("../README.md")]
#[cfg(feature = "broker")]
use kanidm_hsm_crypto::provider::SoftTpm;
#[cfg(all(feature = "tpm", feature = "broker"))]
use kanidm_hsm_crypto::provider::TssTpm;
#[cfg(feature = "broker")]
use kanidm_hsm_crypto::{
provider::BoxedDynTpm as BoxedDynTpmIn, provider::Tpm,
structures::LoadableMsDeviceEnrolmentKey as LoadableMsDeviceEnrolmentKeyIn,
structures::LoadableMsHelloKey as LoadableMsHelloKeyIn,
structures::LoadableRS256Key as LoadableMsOapxbcRsaKeyIn,
structures::LoadableStorageKey as LoadableMachineKeyIn, structures::SealedData as SealedDataIn,
structures::StorageKey as MachineKeyIn, AuthValue,
};
use pastey::paste;
use std::ffi::CString;
use std::os::raw::{c_char, c_int};
use std::slice;
#[cfg(feature = "broker")]
use std::str::FromStr;
#[cfg(feature = "broker")]
use tokio::runtime;
use tracing::{error, warn, Level};
use tracing_subscriber::{EnvFilter, FmtSubscriber};
use crate::auth::*;
use crate::c_helper::*;
use crate::c_helper::{make_error, no_error, MSAL_ERROR, MSAL_ERROR_CODE};
#[cfg(feature = "on_behalf_of")]
use crate::confidential_client::{
ClientCredential, ClientToken, ConfidentialClientApplication, OboToken,
};
use crate::serializer::{deserialize_obj, serialize_obj};
#[cfg(feature = "broker")]
use crate::{EnrollAttrs, EntraSshCertificate};
#[cfg(feature = "broker")]
pub struct BoxedDynTpm(BoxedDynTpmIn);
#[cfg(feature = "broker")]
pub struct LoadableMsDeviceEnrolmentKey(LoadableMsDeviceEnrolmentKeyIn);
#[cfg(feature = "broker")]
pub struct LoadableMsHelloKey(LoadableMsHelloKeyIn);
#[cfg(feature = "broker")]
pub struct LoadableMsOapxbcRsaKey(LoadableMsOapxbcRsaKeyIn);
#[cfg(feature = "broker")]
pub struct MachineKey(MachineKeyIn);
#[cfg(feature = "broker")]
pub struct LoadableMachineKey(LoadableMachineKeyIn);
#[cfg(feature = "broker")]
pub struct SealedData(SealedDataIn);
macro_rules! serialize_and_deserialize_funcs {
($type:ty) => {
paste! {
#[no_mangle]
#[doc = "Serialize a `" $type "` object to bytes."]
pub unsafe extern "C" fn [<serialize _ $type:snake:lower>] (value: &$type,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> *mut MSAL_ERROR {
let bytes = match serialize_obj(&value.0) {
Ok(bytes) => bytes,
Err(e) => {
return make_error(MSAL_ERROR_CODE::INVALID_JSON, e.to_string());
},
};
let mut bytes = std::mem::ManuallyDrop::new(bytes);
unsafe {
*out_buf = bytes.as_mut_ptr();
*out_len = bytes.len();
}
no_error()
}
#[no_mangle]
#[doc = "Deserialize a `" $type "` object from bytes."]
pub unsafe extern "C" fn [<deserialize _ $type:snake:lower>] (in_buf: *mut u8,
in_len: usize,
out: *mut *mut $type,
) -> *mut MSAL_ERROR {
let bytes = slice::from_raw_parts(in_buf, in_len);
let res = match deserialize_obj(bytes) {
Ok(res) => res,
Err(e) => {
return make_error(MSAL_ERROR_CODE::INVALID_JSON, e.to_string());
},
};
unsafe {
*out = Box::into_raw(Box::new($type(res)));
}
no_error()
}
}
};
}
serialize_and_deserialize_funcs!(LoadableMachineKey);
serialize_and_deserialize_funcs!(LoadableMsOapxbcRsaKey);
serialize_and_deserialize_funcs!(LoadableMsDeviceEnrolmentKey);
serialize_and_deserialize_funcs!(LoadableMsHelloKey);
serialize_and_deserialize_funcs!(SealedData);
#[no_mangle]
pub unsafe extern "C" fn raw_serialized_free(input: *mut u8, len: usize) {
if !input.is_null() {
unsafe {
let _ = Vec::from_raw_parts(input, len, len);
}
}
}
#[repr(C)]
pub enum TracingLevel {
ERROR,
WARN,
INFO,
DEBUG,
TRACE,
}
impl From<TracingLevel> for Level {
fn from(level: TracingLevel) -> Self {
match level {
TracingLevel::ERROR => Level::ERROR,
TracingLevel::WARN => Level::WARN,
TracingLevel::INFO => Level::INFO,
TracingLevel::DEBUG => Level::DEBUG,
TracingLevel::TRACE => Level::TRACE,
}
}
}
#[no_mangle]
pub extern "C" fn set_global_tracing_level(level: TracingLevel) -> *mut MSAL_ERROR {
let level: Level = level.into();
let subscriber = FmtSubscriber::builder().with_max_level(level).finish();
match tracing::subscriber::set_global_default(subscriber) {
Ok(_) => no_error(),
Err(e) => make_error(MSAL_ERROR_CODE::GENERAL_FAILURE, e.to_string()),
}
}
#[no_mangle]
pub extern "C" fn set_module_tracing_filter(filter: *const c_char) -> *mut MSAL_ERROR {
let filter_str = match wrap_c_char(filter) {
Some(s) => s,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid filter string".to_string(),
);
}
};
let subscriber = FmtSubscriber::builder()
.with_env_filter(EnvFilter::new(filter_str))
.finish();
match tracing::subscriber::set_global_default(subscriber) {
Ok(_) => no_error(),
Err(e) => make_error(MSAL_ERROR_CODE::GENERAL_FAILURE, e.to_string()),
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn tpm_init(
tcti_name: *const c_char,
out: *mut *mut BoxedDynTpm,
) -> *mut MSAL_ERROR {
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let tpm = BoxedDynTpm(match wrap_c_char(tcti_name) {
#[cfg(feature = "tpm")]
Some(tcti_name) => match TssTpm::new(&tcti_name) {
Ok(tpm_tss) => BoxedDynTpmIn::new(tpm_tss),
Err(e) => {
return make_error(MSAL_ERROR_CODE::TPM_FAIL, format!("{:?}", e));
}
},
#[cfg(not(feature = "tpm"))]
Some(_) => {
warn!(
"{} not built with tpm feature. Hardware tpm request ignored.",
env!("CARGO_PKG_NAME")
);
BoxedDynTpmIn::new(SoftTpm::new())
}
None => BoxedDynTpmIn::new(SoftTpm::new()),
});
unsafe {
*out = Box::into_raw(Box::new(tpm));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn auth_value_generate(out: *mut *mut c_char) -> *mut MSAL_ERROR {
match AuthValue::generate() {
Ok(auth_str) => {
unsafe {
*out = wrap_string(&auth_str);
}
no_error()
}
Err(e) => make_error(MSAL_ERROR_CODE::NO_MEMORY, format!("{:?}", e)),
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn tpm_machine_key_create(
tpm: *mut BoxedDynTpm,
auth_value: *const c_char,
out: *mut *mut LoadableMachineKey,
) -> *mut MSAL_ERROR {
let tpm = &mut unsafe { &mut *tpm }.0;
let auth_str = match wrap_c_char(auth_value) {
Some(auth_str) => auth_str,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid auth_value parameter!".to_string(),
);
}
};
let auth_value = match AuthValue::from_str(&auth_str) {
Ok(auth_value) => auth_value,
Err(e) => {
return make_error(MSAL_ERROR_CODE::TPM_FAIL, format!("{:?}", e));
}
};
let loadable_machine_key = match tpm.root_storage_key_create(&auth_value) {
Ok(loadable_machine_key) => loadable_machine_key,
Err(e) => {
return make_error(MSAL_ERROR_CODE::TPM_FAIL, format!("{:?}", e));
}
};
unsafe {
*out = Box::into_raw(Box::new(LoadableMachineKey(loadable_machine_key)));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn tpm_machine_key_load(
tpm: *mut BoxedDynTpm,
auth_value: *const c_char,
exported_key: *mut LoadableMachineKey,
out: *mut *mut MachineKey,
) -> *mut MSAL_ERROR {
let tpm = &mut unsafe { &mut *tpm }.0;
let auth_str = match wrap_c_char(auth_value) {
Some(auth_str) => auth_str,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid auth_value parameter!".to_string(),
);
}
};
let auth_value = match AuthValue::from_str(&auth_str) {
Ok(auth_value) => auth_value,
Err(e) => {
return make_error(MSAL_ERROR_CODE::TPM_FAIL, format!("{:?}", e));
}
};
let exported_key = &mut unsafe { &mut *exported_key }.0;
let machine_key = match tpm.root_storage_key_load(&auth_value, exported_key) {
Ok(machine_key) => machine_key,
Err(e) => {
return make_error(MSAL_ERROR_CODE::TPM_FAIL, format!("{:?}", e));
}
};
unsafe {
*out = Box::into_raw(Box::new(MachineKey(machine_key)));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_init(
authority: *const c_char,
client_id: *const c_char,
transport_key: *mut LoadableMsOapxbcRsaKey,
cert_key: *mut LoadableMsDeviceEnrolmentKey,
#[cfg(feature = "set_timeout")] timeout_secs: *const u64,
out: *mut *mut BrokerClientApplication,
) -> *mut MSAL_ERROR {
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let transport_key = match transport_key.is_null() {
true => None,
false => Some(unsafe { &mut *transport_key }.0.clone()),
};
let cert_key = match cert_key.is_null() {
true => None,
false => Some(unsafe { &mut *cert_key }.0.clone()),
};
match BrokerClientApplication::new(
wrap_c_char(authority).as_deref(),
wrap_c_char(client_id).as_deref(),
transport_key,
cert_key,
#[cfg(feature = "set_timeout")]
if timeout_secs.is_null() {
std::time::Duration::from_secs(3)
} else {
std::time::Duration::from_secs(unsafe { *timeout_secs })
},
#[cfg(feature = "ipvers")]
&[IpVersion::V4, IpVersion::V6],
) {
Ok(client) => {
unsafe {
*out = Box::into_raw(Box::new(client));
}
no_error()
}
Err(e) => {
let msg = e.to_string();
make_error(MSAL_ERROR_CODE::from(e), msg)
}
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn enroll_attrs_init(
target_domain: *const c_char,
device_display_name: *mut c_char,
device_type: *mut c_char,
join_type: c_int,
os_version: *mut c_char,
out: *mut *mut EnrollAttrs,
) -> *mut MSAL_ERROR {
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
match EnrollAttrs::new(
match wrap_c_char(target_domain) {
Some(target_domain) => target_domain,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid target_domain parameter!".to_string(),
);
}
},
wrap_c_char(device_display_name),
wrap_c_char(device_type),
Some(join_type as u32),
wrap_c_char(os_version),
) {
Ok(attrs) => {
unsafe {
*out = Box::into_raw(Box::new(attrs));
}
no_error()
}
Err(e) => {
let msg = e.to_string();
make_error(MSAL_ERROR_CODE::from(e), msg)
}
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_enroll_device(
client: *mut BrokerClientApplication,
refresh_token: *mut c_char,
attrs: *mut EnrollAttrs,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out_transport_key: *mut *mut LoadableMsOapxbcRsaKey,
out_cert_key: *mut *mut LoadableMsDeviceEnrolmentKey,
out_device_id: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if client.is_null() || attrs.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out_transport_key.is_null() || out_cert_key.is_null() || out_device_id.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameters!".to_string(),
);
}
let client = unsafe { &mut *client };
let refresh_token = match wrap_c_char(refresh_token) {
Some(refresh_token) => refresh_token,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid refresh_token input!".to_string(),
);
}
};
let attrs = unsafe { Box::from_raw(attrs) };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let (transport_key, cert_key, device_id) = match run_async!(
client,
enroll_device,
&refresh_token,
*attrs,
&mut tpm.0,
&machine_key.0,
) {
Ok(resp) => resp,
Err(e) => return e,
};
let c_device_id = match CString::new(device_id) {
Ok(c_device_id) => c_device_id,
Err(e) => {
return make_error(MSAL_ERROR_CODE::NO_MEMORY, e.to_string());
}
};
unsafe {
*out_transport_key = Box::into_raw(Box::new(LoadableMsOapxbcRsaKey(transport_key)));
*out_cert_key = Box::into_raw(Box::new(LoadableMsDeviceEnrolmentKey(cert_key)));
*out_device_id = c_device_id.into_raw();
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_token_by_username_password(
client: *mut BrokerClientApplication,
username: *const c_char,
password: *const c_char,
scopes: *const *const c_char,
scopes_len: c_int,
request_resource: *const c_char,
#[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if client.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let password = match wrap_c_char(password) {
Some(password) => password,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input password!".to_string(),
);
}
};
let scopes = match str_array_to_vec(scopes, scopes_len) {
Ok(scopes) => scopes,
Err(e) => return e,
};
let request_resource = wrap_c_char(request_resource);
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
#[cfg(feature = "on_behalf_of")]
let on_behalf_of_client_id = wrap_c_char(on_behalf_of_client_id);
let resp = match run_async!(
client,
acquire_token_by_username_password,
&username,
&password,
str_vec_ref!(scopes),
request_resource,
#[cfg(feature = "on_behalf_of")]
on_behalf_of_client_id.as_deref(),
&mut tpm.0,
&machine_key.0,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_token_by_refresh_token(
client: *mut BrokerClientApplication,
refresh_token: *const c_char,
scopes: *const *const c_char,
scopes_len: c_int,
request_resource: *const c_char,
#[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if client.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let refresh_token = match wrap_c_char(refresh_token) {
Some(refresh_token) => refresh_token,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input refresh_token!".to_string(),
);
}
};
let scopes = match str_array_to_vec(scopes, scopes_len) {
Ok(scopes) => scopes,
Err(e) => return e,
};
let request_resource = wrap_c_char(request_resource);
#[cfg(feature = "on_behalf_of")]
let on_behalf_of_client_id = wrap_c_char(on_behalf_of_client_id);
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let resp = match run_async!(
client,
acquire_token_by_refresh_token,
&refresh_token,
str_vec_ref!(scopes),
request_resource,
#[cfg(feature = "on_behalf_of")]
on_behalf_of_client_id.as_deref(),
&mut tpm.0,
&machine_key.0,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_initiate_authorization_code_pkce_flow(
client: *mut BrokerClientApplication,
scopes: *const *const c_char,
scopes_len: c_int,
redirect_uri: *const c_char,
out: *mut *mut AuthorizationCodePkceFlow,
) -> *mut MSAL_ERROR {
if client.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input client!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let scopes = match str_array_to_vec(scopes, scopes_len) {
Ok(scopes) => scopes,
Err(e) => return e,
};
let redirect_uri = match wrap_c_char(redirect_uri) {
Some(redirect_uri) => redirect_uri,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input redirect_uri!".to_string(),
);
}
};
match client.initiate_authorization_code_pkce_flow(str_vec_ref!(scopes), &redirect_uri) {
Ok(flow) => {
unsafe {
*out = Box::into_raw(Box::new(flow));
}
no_error()
}
Err(e) => {
let msg = e.to_string();
make_error(MSAL_ERROR_CODE::from(e), msg)
}
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_token_by_authorization_code_pkce_flow(
client: *mut BrokerClientApplication,
flow: *mut AuthorizationCodePkceFlow,
redirect_url: *const c_char,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if client.is_null() || flow.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let flow = unsafe { &mut *flow };
let redirect_url = match wrap_c_char(redirect_url) {
Some(redirect_url) => redirect_url,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input redirect_url!".to_string(),
);
}
};
let resp = match run_async!(
client,
acquire_token_by_authorization_code_pkce_flow,
flow,
&redirect_url,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_token_by_username_password_for_device_enrollment(
client: *mut BrokerClientApplication,
username: *const c_char,
password: *const c_char,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if client.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let password = match wrap_c_char(password) {
Some(password) => password,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input password!".to_string(),
);
}
};
let resp = match run_async!(
client,
acquire_token_by_username_password_for_device_enrollment,
&username,
&password,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_initiate_device_flow_for_device_enrollment(
client: *mut BrokerClientApplication,
out: *mut *mut DeviceAuthorizationResponse,
) -> *mut MSAL_ERROR {
if client.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let resp = match run_async!(
client,
initiate_device_flow_for_device_enrollment,
#[cfg(feature = "optional_mfa")]
&[],
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_token_by_device_flow(
client: *mut BrokerClientApplication,
flow: *mut DeviceAuthorizationResponse,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if client.is_null() || flow.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let flow = unsafe { &mut *flow }.clone();
let resp = match run_async!(client, acquire_token_by_device_flow, flow) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_check_user_exists(
client: *mut BrokerClientApplication,
username: *const c_char,
out: *mut bool,
) -> *mut MSAL_ERROR {
if client.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let resp = match run_async!(client, check_user_exists, &username, &[]) {
Ok(resp) => resp,
Err(e) => return e,
};
#[allow(deprecated)]
unsafe {
*out = resp.exists();
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_initiate_acquire_token_by_mfa_flow(
client: *mut BrokerClientApplication,
username: *const c_char,
password: *const c_char,
options: *const AuthOption,
options_len: usize,
out: *mut *mut MFAAuthContinue,
) -> *mut MSAL_ERROR {
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let password_is_null = password.is_null();
let password = wrap_c_char(password);
if password.is_none() && !password_is_null {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input password!".to_string(),
);
}
let options: &[AuthOption] = if options.is_null() || options_len == 0 {
&[]
} else {
unsafe { slice::from_raw_parts(options, options_len) }
};
#[cfg(not(feature = "mfa_method_selection"))]
let flow = match run_async!(
client,
initiate_acquire_token_by_mfa_flow,
&username,
password.as_deref(),
options,
None,
) {
Ok(resp) => resp,
Err(e) => return e,
};
#[cfg(feature = "mfa_method_selection")]
let flow = match run_async!(
client,
initiate_acquire_token_by_mfa_flow,
&username,
password.as_deref(),
options,
None,
None, ) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(flow));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_initiate_acquire_token_by_mfa_flow_for_device_enrollment(
client: *mut BrokerClientApplication,
username: *const c_char,
password: *const c_char,
options: *const AuthOption,
options_len: usize,
out: *mut *mut MFAAuthContinue,
) -> *mut MSAL_ERROR {
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let password_is_null = password.is_null();
let password = wrap_c_char(password);
if password.is_none() && !password_is_null {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input password!".to_string(),
);
}
let options: &[AuthOption] = if options.is_null() || options_len == 0 {
&[]
} else {
unsafe { slice::from_raw_parts(options, options_len) }
};
#[cfg(not(feature = "mfa_method_selection"))]
let flow = match run_async!(
client,
initiate_acquire_token_by_mfa_flow_for_device_enrollment,
&username,
password.as_deref(),
options,
None,
) {
Ok(resp) => resp,
Err(e) => return e,
};
#[cfg(feature = "mfa_method_selection")]
let flow = match run_async!(
client,
initiate_acquire_token_by_mfa_flow_for_device_enrollment,
&username,
password.as_deref(),
options,
None,
None, ) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(flow));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_token_by_mfa_flow(
client: *mut BrokerClientApplication,
username: *const c_char,
auth_data: *const c_char,
poll_attempt: c_int,
flow: *mut MFAAuthContinue,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let auth_data = wrap_c_char(auth_data);
let poll_attempt = match auth_data {
Some(_) => None,
None => Some(poll_attempt as u32),
};
let flow = unsafe { &mut *flow };
let resp = match run_async!(
client,
acquire_token_by_mfa_flow,
&username,
auth_data.as_deref(),
poll_attempt,
flow,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_msg(
flow: *mut MFAAuthContinue,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(flow, msg, out)
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_mfa_method(
flow: *mut MFAAuthContinue,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if flow.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let flow = unsafe { &mut *flow };
let method_id = flow
.get_default_mfa_method_details()
.map(|info| info.auth_method_id)
.unwrap_or_else(|| {
flow.get_available_mfa_methods()
.first()
.cloned()
.unwrap_or_else(String::new)
});
let c_str = wrap_string(&method_id);
if c_str.is_null() {
return make_error(
MSAL_ERROR_CODE::NO_MEMORY,
"Failed to allocate string".to_string(),
);
}
unsafe {
*out = c_str;
}
no_error()
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_polling_interval(flow: *mut MFAAuthContinue) -> c_int {
let flow = unsafe { &mut *flow };
match flow.polling_interval {
Some(polling_interval) => polling_interval as c_int,
None => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_max_poll_attempts(flow: *mut MFAAuthContinue) -> c_int {
let flow = unsafe { &mut *flow };
match flow.max_poll_attempts {
Some(max_poll_attempts) => max_poll_attempts as c_int,
None => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_fido_challenge(
flow: *mut MFAAuthContinue,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if flow.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let flow = unsafe { &mut *flow };
match &flow.fido_challenge {
Some(challenge) => {
let c_str = wrap_string(challenge);
if c_str.is_null() {
return make_error(
MSAL_ERROR_CODE::NO_MEMORY,
"Failed to allocate string".to_string(),
);
}
unsafe {
*out = c_str;
}
}
None => unsafe {
*out = std::ptr::null_mut();
},
}
no_error()
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_fido_allow_list(
flow: *mut MFAAuthContinue,
out_list: *mut *mut *mut c_char,
out_count: *mut c_int,
) -> *mut MSAL_ERROR {
if flow.is_null() || out_list.is_null() || out_count.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let flow = unsafe { &mut *flow };
let allow_list = match &flow.fido_allow_list {
Some(allow_list) if !allow_list.is_empty() => allow_list,
_ => {
unsafe {
*out_list = std::ptr::null_mut();
*out_count = 0;
}
return no_error();
}
};
let mut c_strings: Vec<*mut c_char> = Vec::with_capacity(allow_list.len());
for cred_id in allow_list.iter() {
let c_str = wrap_string(cred_id);
if c_str.is_null() {
for prev_str in c_strings {
if !prev_str.is_null() {
unsafe {
let _ = CString::from_raw(prev_str);
}
}
}
return make_error(
MSAL_ERROR_CODE::NO_MEMORY,
"Failed to convert allow list entry".to_string(),
);
}
c_strings.push(c_str);
}
let count = c_strings.len();
let boxed_slice = c_strings.into_boxed_slice();
let raw_ptr: *mut [*mut c_char] = Box::into_raw(boxed_slice);
unsafe {
*out_list = raw_ptr as *mut *mut c_char;
*out_count = count as c_int;
}
no_error()
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_free_fido_allow_list(
list: *mut *mut c_char,
count: c_int,
) {
if list.is_null() || count <= 0 {
return;
}
let slice = unsafe { std::slice::from_raw_parts_mut(list, count as usize) };
let boxed_slice = unsafe { Box::from_raw(slice) };
for str_ptr in boxed_slice.iter() {
if !str_ptr.is_null() {
unsafe {
let _ = CString::from_raw(*str_ptr);
}
}
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_user_prt_by_username_password(
client: *mut BrokerClientApplication,
username: *const c_char,
password: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut SealedData,
) -> *mut MSAL_ERROR {
if client.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let password = match wrap_c_char(password) {
Some(password) => password,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input password!".to_string(),
);
}
};
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let resp = match run_async!(
client,
acquire_user_prt_by_username_password,
&username,
&password,
&mut tpm.0,
&machine_key.0,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(SealedData(resp)));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_user_prt_by_refresh_token(
client: *mut BrokerClientApplication,
refresh_token: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut SealedData,
) -> *mut MSAL_ERROR {
if client.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let refresh_token = match wrap_c_char(refresh_token) {
Some(refresh_token) => refresh_token,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input refresh_token!".to_string(),
);
}
};
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let resp = match run_async!(
client,
acquire_user_prt_by_refresh_token,
&refresh_token,
&mut tpm.0,
&machine_key.0,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(SealedData(resp)));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_exchange_prt_for_access_token(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
scopes: *const *const c_char,
scopes_len: c_int,
request_resource: *const c_char,
#[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
broker_exchange_prt_for_access_token_with_pop(
client,
sealed_prt,
scopes,
scopes_len,
request_resource,
#[cfg(feature = "on_behalf_of")]
on_behalf_of_client_id,
std::ptr::null(),
tpm,
machine_key,
out,
)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_exchange_prt_for_access_token_with_pop(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
scopes: *const *const c_char,
scopes_len: c_int,
request_resource: *const c_char,
#[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: *const c_char,
req_cnf: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if client.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let sealed_prt = unsafe { &mut *sealed_prt };
let scopes = match str_array_to_vec(scopes, scopes_len) {
Ok(scopes) => scopes,
Err(e) => return e,
};
let request_resource = wrap_c_char(request_resource);
#[cfg(feature = "on_behalf_of")]
let on_behalf_of_client_id = wrap_c_char(on_behalf_of_client_id);
let req_cnf = wrap_c_char(req_cnf);
#[cfg(not(feature = "pop_support"))]
if req_cnf.is_some() {
return make_error(
MSAL_ERROR_CODE::NOT_IMPLEMENTED,
"PoP token support requires libhimmelblau to be built with pop_support".to_string(),
);
}
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let resp = match run_async!(
client,
exchange_prt_for_access_token,
&sealed_prt.0,
str_vec_ref!(scopes),
request_resource,
#[cfg(feature = "on_behalf_of")]
on_behalf_of_client_id.as_deref(),
&mut tpm.0,
&machine_key.0,
#[cfg(feature = "redirect_uri")]
None,
#[cfg(feature = "pop_support")]
req_cnf.as_deref(),
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_exchange_prt_for_prt(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
request_tgt: c_int,
out: *mut *mut SealedData,
) -> *mut MSAL_ERROR {
if client.is_null() || sealed_prt.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let sealed_prt = unsafe { &mut *sealed_prt };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let request_tgt = request_tgt != 0;
let resp = match run_async!(
client,
exchange_prt_for_prt,
&sealed_prt.0,
&mut tpm.0,
&machine_key.0,
request_tgt,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(SealedData(resp)));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_provision_hello_for_business_key(
client: *mut BrokerClientApplication,
token: *mut UserToken,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
pin: *const c_char,
out: *mut *mut LoadableMsHelloKey,
) -> *mut MSAL_ERROR {
if client.is_null() || token.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let token = unsafe { &mut *token };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let pin = match wrap_c_char(pin) {
Some(pin) => pin,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input pin!".to_string(),
);
}
};
let resp = match run_async!(
client,
provision_hello_for_business_key,
token,
&mut tpm.0,
&machine_key.0,
&pin,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(LoadableMsHelloKey(resp)));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_token_by_hello_for_business_key(
client: *mut BrokerClientApplication,
username: *const c_char,
key: *mut LoadableMsHelloKey,
scopes: *const *const c_char,
scopes_len: c_int,
request_resource: *const c_char,
#[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
pin: *const c_char,
out: *mut *mut UserToken,
) -> *mut MSAL_ERROR {
if client.is_null() || key.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let key = unsafe { &mut *key };
let scopes = match str_array_to_vec(scopes, scopes_len) {
Ok(scopes) => scopes,
Err(e) => return e,
};
let request_resource = wrap_c_char(request_resource);
#[cfg(feature = "on_behalf_of")]
let on_behalf_of_client_id = wrap_c_char(on_behalf_of_client_id);
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let pin = match wrap_c_char(pin) {
Some(pin) => pin,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input pin!".to_string(),
);
}
};
let resp = match run_async!(
client,
acquire_token_by_hello_for_business_key,
&username,
&key.0,
str_vec_ref!(scopes),
request_resource,
#[cfg(feature = "on_behalf_of")]
on_behalf_of_client_id.as_deref(),
&mut tpm.0,
&machine_key.0,
&pin,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_user_prt_by_hello_for_business_key(
client: *mut BrokerClientApplication,
username: *const c_char,
key: *mut LoadableMsHelloKey,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
pin: *const c_char,
out: *mut *mut SealedData,
) -> *mut MSAL_ERROR {
if client.is_null() || key.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let username = match wrap_c_char(username) {
Some(username) => username,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let key = unsafe { &mut *key };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let pin = match wrap_c_char(pin) {
Some(pin) => pin,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input pin!".to_string(),
);
}
};
let resp = match run_async!(
client,
acquire_user_prt_by_hello_for_business_key,
&username,
&key.0,
&mut tpm.0,
&machine_key.0,
&pin,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(SealedData(resp)));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_prt_sso_cookie(
client: *mut BrokerClientApplication,
prt: *mut SealedData,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if client.is_null() || prt.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let prt = unsafe { &mut *prt };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let resp = match run_async!(
client,
acquire_prt_sso_cookie,
&prt.0,
&mut tpm.0,
&machine_key.0,
) {
Ok(jwt) => jwt,
Err(e) => return e,
};
let c_str = wrap_string(&resp);
if !c_str.is_null() {
unsafe {
*out = c_str;
}
no_error()
} else {
make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid response from acquire_prt_sso_cookie".to_string(),
)
}
}
#[no_mangle]
pub unsafe extern "C" fn user_token_refresh_token(
token: *mut UserToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(token, refresh_token, out)
}
#[no_mangle]
pub unsafe extern "C" fn user_token_access_token(
token: *mut UserToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_option_string!(token, access_token, out)
}
#[no_mangle]
pub unsafe extern "C" fn user_token_tenant_id(
token: *mut UserToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_func!(token, tenant_id, out)
}
#[no_mangle]
pub unsafe extern "C" fn user_token_spn(
token: *mut UserToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_func!(token, spn, out)
}
#[no_mangle]
pub unsafe extern "C" fn user_token_uuid(
token: *mut UserToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
let token = unsafe { &mut *token };
match token.uuid() {
Ok(uuid) => {
let c_str = wrap_string(&uuid.to_string());
if !c_str.is_null() {
unsafe {
*out = c_str;
}
no_error()
} else {
make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid response token.uuid()".to_string(),
)
}
}
Err(e) => make_error(MSAL_ERROR_CODE::INVALID_POINTER, e.to_string()),
}
}
#[no_mangle]
pub unsafe extern "C" fn user_token_amr_mfa(
token: *mut UserToken,
out: *mut bool,
) -> *mut MSAL_ERROR {
let token = unsafe { &mut *token };
match token.amr_mfa() {
Ok(res) => {
unsafe {
*out = res;
}
no_error()
}
Err(e) => make_error(MSAL_ERROR_CODE::INVALID_POINTER, e.to_string()),
}
}
#[no_mangle]
pub unsafe extern "C" fn user_token_prt(
token: *mut UserToken,
out: *mut *mut SealedData,
) -> *mut MSAL_ERROR {
let token = unsafe { &mut *token };
match &token.prt {
Some(prt) => {
unsafe {
*out = Box::into_raw(Box::new(SealedData(prt.clone())));
}
no_error()
}
None => make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"PRT not found!".to_string(),
),
}
}
#[no_mangle]
pub unsafe extern "C" fn authorization_code_pkce_flow_auth_url(
flow: *mut AuthorizationCodePkceFlow,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if flow.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
c_str_from_object_string!(flow, auth_url, out)
}
#[no_mangle]
pub unsafe extern "C" fn authorization_code_pkce_flow_redirect_uri(
flow: *mut AuthorizationCodePkceFlow,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if flow.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
c_str_from_object_string!(flow, redirect_uri, out)
}
#[no_mangle]
pub unsafe extern "C" fn authorization_code_pkce_flow_state(
flow: *mut AuthorizationCodePkceFlow,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if flow.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
c_str_from_object_string!(flow, state, out)
}
macro_rules! broker_store_tgt {
($func:ident, $client:ident, $sealed_prt:ident, $filename:ident, $tpm:ident, $machine_key:ident) => {{
if $client.is_null() || $sealed_prt.is_null() || $tpm.is_null() || $machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
let client = unsafe { &mut *$client };
let sealed_prt = unsafe { &mut *$sealed_prt };
let filename = match wrap_c_char($filename) {
Some(filename) => filename,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input username!".to_string(),
);
}
};
let tpm = unsafe { &mut *$tpm };
let machine_key = unsafe { &mut *$machine_key };
match client.$func(&sealed_prt.0, &filename, &mut tpm.0, &machine_key.0) {
Ok(res) => res,
Err(e) => {
let msg = e.to_string();
return make_error(MSAL_ERROR_CODE::from(e), msg);
}
}
no_error()
}};
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_store_cloud_tgt(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
filename: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
) -> *mut MSAL_ERROR {
broker_store_tgt!(
store_cloud_tgt,
client,
sealed_prt,
filename,
tpm,
machine_key
)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_store_ad_tgt(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
filename: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
) -> *mut MSAL_ERROR {
broker_store_tgt!(store_ad_tgt, client, sealed_prt, filename, tpm, machine_key)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_unseal_prt_kerberos_top_level_names(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
let sealed_prt = unsafe { &mut *sealed_prt };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
c_str_from_object_func!(
client,
unseal_prt_kerberos_top_level_names,
out,
&sealed_prt.0,
&mut tpm.0,
&machine_key.0
)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_exchange_prt_for_ssh_certificate(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
openssh_public_key: *const c_char,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut EntraSshCertificate,
) -> *mut MSAL_ERROR {
if client.is_null()
|| sealed_prt.is_null()
|| tpm.is_null()
|| machine_key.is_null()
|| out.is_null()
{
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
let openssh_public_key = match wrap_c_char(openssh_public_key) {
Some(value) => value,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid openssh_public_key!".to_string(),
)
}
};
let client = unsafe { &mut *client };
let sealed_prt = unsafe { &mut *sealed_prt };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let certificate = match run_async!(
client,
exchange_prt_for_ssh_certificate,
&sealed_prt.0,
&openssh_public_key,
&mut tpm.0,
&machine_key.0,
) {
Ok(value) => value,
Err(error) => return error,
};
unsafe { *out = Box::into_raw(Box::new(certificate)) };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_openssh(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let value = unsafe { &*certificate }.openssh_certificate();
unsafe { *out = wrap_string(&value) };
if unsafe { *out }.is_null() {
make_error(
MSAL_ERROR_CODE::NO_MEMORY,
"Failed allocating string".to_string(),
)
} else {
no_error()
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_request_key_id(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(certificate, request_key_id, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_body_base64(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(certificate, certificate_body_base64, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_key_id(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(certificate, certificate_key_id, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_tenant_id(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let value = unsafe { &*certificate }.tenant_id.to_string();
unsafe { *out = wrap_string(&value) };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_object_id(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let value = unsafe { &*certificate }.object_id.to_string();
unsafe { *out = wrap_string(&value) };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_display_name(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_option_string!(certificate, display_name, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_ca_fingerprint_sha256(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(certificate, signing_ca_fingerprint_sha256, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_ca_openssh_public_key(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(certificate, signing_ca_openssh_public_key, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_scope(
certificate: *mut EntraSshCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_option_string!(certificate, scope, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_principals(
certificate: *mut EntraSshCertificate,
out_list: *mut *mut *mut c_char,
out_count: *mut c_int,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out_list.is_null() || out_count.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let principals = &unsafe { &*certificate }.principals;
let mut strings: Vec<*mut c_char> = Vec::with_capacity(principals.len());
for principal in principals {
let value = wrap_string(principal);
if value.is_null() {
for prior in strings {
if !prior.is_null() {
unsafe { drop(CString::from_raw(prior)) };
}
}
return make_error(
MSAL_ERROR_CODE::NO_MEMORY,
"Failed allocating string".to_string(),
);
}
strings.push(value);
}
let count = strings.len();
let raw = Box::into_raw(strings.into_boxed_slice());
unsafe {
*out_list = raw as *mut *mut c_char;
*out_count = count as c_int;
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_free_principals(list: *mut *mut c_char, count: c_int) {
p2p_certificate_free_dns_names(list, count)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_valid_after(
certificate: *mut EntraSshCertificate,
out: *mut u64,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
unsafe { *out = (*certificate).valid_after };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_valid_before(
certificate: *mut EntraSshCertificate,
out: *mut u64,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
unsafe { *out = (*certificate).valid_before };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_serial(
certificate: *mut EntraSshCertificate,
out: *mut u64,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
unsafe { *out = (*certificate).serial };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_expires_in(
certificate: *mut EntraSshCertificate,
out: *mut u32,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
unsafe { *out = (*certificate).expires_in };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_ext_expires_in(
certificate: *mut EntraSshCertificate,
out: *mut u32,
) -> *mut MSAL_ERROR {
if certificate.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
unsafe { *out = (*certificate).ext_expires_in };
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn ssh_certificate_free(certificate: *mut EntraSshCertificate) {
free_object!(certificate);
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_device_p2p_certificate(
client: *mut BrokerClientApplication,
tenant_id: *const c_char,
device_name: *const c_char,
dns_names: *const *const c_char,
dns_names_len: c_int,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut P2PCertificate,
) -> *mut MSAL_ERROR {
if client.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let tenant_id = match wrap_c_char(tenant_id) {
Some(tenant_id) => tenant_id,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input tenant_id!".to_string(),
);
}
};
let device_name = match wrap_c_char(device_name) {
Some(device_name) => device_name,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input device_name!".to_string(),
);
}
};
let dns_names = match str_array_to_vec(dns_names, dns_names_len) {
Ok(dns_names) => dns_names,
Err(e) => return e,
};
let dns_names: Option<Vec<&str>> = if dns_names.is_empty() {
None
} else {
Some(str_vec_ref!(dns_names))
};
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let resp = match run_async!(
client,
acquire_device_p2p_certificate,
&tenant_id,
&device_name,
dns_names.as_deref(),
&mut tpm.0,
&machine_key.0,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_acquire_user_p2p_certificate(
client: *mut BrokerClientApplication,
sealed_prt: *mut SealedData,
tpm: *mut BoxedDynTpm,
machine_key: *mut MachineKey,
out: *mut *mut P2PCertificate,
) -> *mut MSAL_ERROR {
if client.is_null() || sealed_prt.is_null() || tpm.is_null() || machine_key.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid input parameters!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client = unsafe { &mut *client };
let sealed_prt = unsafe { &mut *sealed_prt };
let tpm = unsafe { &mut *tpm };
let machine_key = unsafe { &mut *machine_key };
let resp = match run_async!(
client,
acquire_user_p2p_certificate,
&sealed_prt.0,
&mut tpm.0,
&machine_key.0,
) {
Ok(resp) => resp,
Err(e) => return e,
};
unsafe {
*out = Box::into_raw(Box::new(resp));
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_subject(
cert: *mut P2PCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(cert, subject, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_issuer(
cert: *mut P2PCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(cert, issuer, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_thumbprint_sha1(
cert: *mut P2PCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_string!(cert, thumbprint_sha1, out)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_ca_certificate_pem(
cert: *mut P2PCertificate,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
c_str_from_object_option_string!(cert, ca_certificate_pem, out)
}
macro_rules! p2p_certificate_der {
($cert:ident, $item:ident, $out_buf:ident, $out_len:ident) => {{
if $cert.is_null() || $out_buf.is_null() || $out_len.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let cert = unsafe { &mut *$cert };
let mut bytes = std::mem::ManuallyDrop::new(cert.$item.clone().into_boxed_slice());
unsafe {
*$out_buf = bytes.as_mut_ptr();
*$out_len = bytes.len();
}
no_error()
}};
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_certificate_der(
cert: *mut P2PCertificate,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> *mut MSAL_ERROR {
p2p_certificate_der!(cert, certificate_der, out_buf, out_len)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_ca_certificate_der(
cert: *mut P2PCertificate,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> *mut MSAL_ERROR {
p2p_certificate_der!(cert, ca_certificate_der, out_buf, out_len)
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_dns_names(
cert: *mut P2PCertificate,
out_list: *mut *mut *mut c_char,
out_count: *mut c_int,
) -> *mut MSAL_ERROR {
if cert.is_null() || out_list.is_null() || out_count.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let cert = unsafe { &mut *cert };
if cert.dns_names.is_empty() {
unsafe {
*out_list = std::ptr::null_mut();
*out_count = 0;
}
return no_error();
}
let mut c_strings: Vec<*mut c_char> = Vec::with_capacity(cert.dns_names.len());
for dns_name in cert.dns_names.iter() {
let c_str = wrap_string(dns_name);
if c_str.is_null() {
for prev_str in c_strings {
if !prev_str.is_null() {
unsafe {
let _ = CString::from_raw(prev_str);
}
}
}
return make_error(
MSAL_ERROR_CODE::NO_MEMORY,
"Failed to convert DNS name".to_string(),
);
}
c_strings.push(c_str);
}
let count = c_strings.len();
let boxed_slice = c_strings.into_boxed_slice();
let raw_ptr: *mut [*mut c_char] = Box::into_raw(boxed_slice);
unsafe {
*out_list = raw_ptr as *mut *mut c_char;
*out_count = count as c_int;
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_free_dns_names(list: *mut *mut c_char, count: c_int) {
if list.is_null() || count <= 0 {
return;
}
let slice = unsafe { std::slice::from_raw_parts_mut(list, count as usize) };
let boxed_slice = unsafe { Box::from_raw(slice) };
for str_ptr in boxed_slice.iter() {
if !str_ptr.is_null() {
unsafe {
let _ = CString::from_raw(*str_ptr);
}
}
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_not_after(
cert: *mut P2PCertificate,
out: *mut i64,
) -> *mut MSAL_ERROR {
if cert.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let cert = unsafe { &mut *cert };
unsafe {
*out = cert.not_after_unix;
}
no_error()
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_device_private_key(
cert: *mut P2PCertificate,
out: *mut *mut LoadableMsDeviceEnrolmentKey,
) -> *mut MSAL_ERROR {
if cert.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let cert = unsafe { &mut *cert };
match &cert.private_key {
P2PPrivateKey::ExistingDevice(key) => {
unsafe {
*out = Box::into_raw(Box::new(LoadableMsDeviceEnrolmentKey(key.clone())));
}
no_error()
}
P2PPrivateKey::GeneratedUser(_) => make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"This certificate is not bound to a device enrolment key!".to_string(),
),
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_user_private_key(
cert: *mut P2PCertificate,
out: *mut *mut LoadableMsOapxbcRsaKey,
) -> *mut MSAL_ERROR {
if cert.is_null() || out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid parameters".to_string(),
);
}
let cert = unsafe { &mut *cert };
match &cert.private_key {
P2PPrivateKey::GeneratedUser(key) => {
unsafe {
*out = Box::into_raw(Box::new(LoadableMsOapxbcRsaKey(key.clone())));
}
no_error()
}
P2PPrivateKey::ExistingDevice(_) => make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"This certificate is not bound to a generated user key!".to_string(),
),
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn p2p_certificate_free(input: *mut P2PCertificate) {
free_object!(input);
}
#[no_mangle]
pub unsafe extern "C" fn error_free(error: *mut MSAL_ERROR) {
if !error.is_null() {
let error = Box::from_raw(error);
if !error.msg.is_null() {
drop(CString::from_raw(error.msg as *mut c_char));
}
if !error.claims.is_null() {
drop(CString::from_raw(error.claims as *mut c_char));
}
if !error.acquire_token_error_codes.is_null() && error.acquire_token_error_codes_len > 0 {
let slice_ptr = std::ptr::slice_from_raw_parts_mut(
error.acquire_token_error_codes,
error.acquire_token_error_codes_len,
);
let _ = Box::from_raw(slice_ptr);
}
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn broker_free(client: *mut BrokerClientApplication) {
free_object!(client);
}
#[no_mangle]
pub unsafe extern "C" fn string_free(input: *mut c_char) {
if !input.is_null() {
unsafe {
let _ = CString::from_raw(input);
}
}
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn machine_key_free(input: *mut MachineKey) {
free_object!(input);
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn loadable_machine_key_free(input: *mut LoadableMachineKey) {
free_object!(input);
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn loadable_ms_oapxbc_rsa_key_free(input: *mut LoadableMsOapxbcRsaKey) {
free_object!(input);
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn loadable_ms_device_enrollment_key_free(
input: *mut LoadableMsDeviceEnrolmentKey,
) {
free_object!(input);
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn loadable_ms_hello_key_free(input: *mut LoadableMsHelloKey) {
free_object!(input);
}
#[no_mangle]
pub unsafe extern "C" fn user_token_free(input: *mut UserToken) {
free_object!(input);
}
#[no_mangle]
pub unsafe extern "C" fn authorization_code_pkce_flow_free(input: *mut AuthorizationCodePkceFlow) {
free_object!(input);
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn tpm_free(input: *mut BoxedDynTpm) {
free_object!(input);
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_method_count(flow: *mut MFAAuthContinue) -> c_int {
let flow = unsafe { &mut *flow };
flow.mfa_method_count() as c_int
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_available_methods(
flow: *mut MFAAuthContinue,
out_methods: *mut *mut *mut c_char,
out_count: *mut c_int,
) -> *mut MSAL_ERROR {
if flow.is_null() || out_methods.is_null() || out_count.is_null() {
error!("Invalid pointers provided");
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid pointers provided".to_string(),
);
}
let flow = unsafe { &mut *flow };
let methods = flow.get_available_mfa_methods();
let count = methods.len();
if count == 0 {
unsafe {
*out_methods = std::ptr::null_mut();
*out_count = 0;
}
return no_error();
}
let mut c_strings: Vec<*mut c_char> = Vec::with_capacity(count);
for method in methods.iter() {
let c_str = wrap_string(method);
if c_str.is_null() {
for prev_str in c_strings {
if !prev_str.is_null() {
unsafe {
let _ = CString::from_raw(prev_str);
}
}
}
error!("Failed to convert method string");
return make_error(
MSAL_ERROR_CODE::NO_MEMORY,
"Failed to convert method string".to_string(),
);
}
c_strings.push(c_str);
}
let boxed_slice = c_strings.into_boxed_slice();
let raw_ptr: *mut [*mut c_char] = Box::into_raw(boxed_slice);
unsafe {
*out_methods = raw_ptr as *mut *mut c_char;
*out_count = count as c_int;
}
no_error()
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_has_method(
flow: *mut MFAAuthContinue,
method_id: *const c_char,
) -> c_int {
if flow.is_null() || method_id.is_null() {
error!("Invalid pointers provided");
return -1;
}
let flow = unsafe { &mut *flow };
let method_id = match wrap_c_char(method_id) {
Some(method_id) => method_id,
None => {
error!("Invalid method_id string");
return -1;
}
};
if flow.has_mfa_method(&method_id) {
1
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_free_methods(methods: *mut *mut c_char, count: c_int) {
if methods.is_null() || count <= 0 {
return;
}
let slice = unsafe { std::slice::from_raw_parts_mut(methods, count as usize) };
let boxed_slice = unsafe { Box::from_raw(slice) };
for str_ptr in boxed_slice.iter() {
if !str_ptr.is_null() {
unsafe {
let _ = CString::from_raw(*str_ptr);
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn mfa_auth_continue_free(input: *mut MFAAuthContinue) {
free_object!(input);
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn confidential_client_init_with_secret(
client_id: *const c_char,
authority: *const c_char,
client_secret: *const c_char,
out: *mut *mut ConfidentialClientApplication,
) -> *mut MSAL_ERROR {
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let client_id_str = match wrap_c_char(client_id) {
Some(s) => s,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid client_id parameter!".to_string(),
);
}
};
let secret_str = match wrap_c_char(client_secret) {
Some(s) => s,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid client_secret parameter!".to_string(),
);
}
};
let authority_opt = wrap_c_char(authority);
let credential = ClientCredential::from_secret(secret_str);
match ConfidentialClientApplication::new(
&client_id_str,
authority_opt.as_deref(),
credential,
#[cfg(feature = "set_timeout")]
std::time::Duration::from_secs(3),
#[cfg(feature = "ipvers")]
&[IpVersion::V4, IpVersion::V6],
) {
Ok(app) => {
unsafe {
*out = Box::into_raw(Box::new(app));
}
no_error()
}
Err(e) => {
let msg = e.to_string();
make_error(MSAL_ERROR_CODE::from(e), msg)
}
}
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn confidential_acquire_token_on_behalf_of(
app: *mut ConfidentialClientApplication,
user_assertion: *const c_char,
scopes: *const *const c_char,
scopes_len: c_int,
out: *mut *mut OboToken,
) -> *mut MSAL_ERROR {
if app.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid app parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let app = unsafe { &mut *app };
let assertion_str = match wrap_c_char(user_assertion) {
Some(s) => s,
None => {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid user_assertion parameter!".to_string(),
);
}
};
let scopes_vec = match str_array_to_vec(scopes, scopes_len) {
Ok(s) => s,
Err(e) => return e,
};
let scopes_ref: Vec<&str> = scopes_vec.iter().map(|s| s.as_str()).collect();
match runtime::Runtime::new() {
Ok(rt) => {
match rt.block_on(async {
app.acquire_token_on_behalf_of(&assertion_str, scopes_ref, None)
.await
}) {
Ok(token) => {
unsafe {
*out = Box::into_raw(Box::new(token));
}
no_error()
}
Err(e) => make_error_from_msal_error(e),
}
}
Err(e) => make_error(MSAL_ERROR_CODE::NO_MEMORY, e.to_string()),
}
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn obo_token_access_token(
token: *mut OboToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
c_str_from_object_string!(token, access_token, out)
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn obo_token_refresh_token(
token: *mut OboToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let token = unsafe { &mut *token };
if let Some(refresh_token) = &token.refresh_token {
let c_str = wrap_string(refresh_token);
if c_str.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token.refresh_token value!".to_string(),
);
}
unsafe {
*out = c_str;
}
} else {
unsafe {
*out = std::ptr::null_mut();
}
}
no_error()
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn obo_token_scope(
token: *mut OboToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let token = unsafe { &mut *token };
if let Some(scope) = &token.scope {
let c_str = wrap_string(scope);
if c_str.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token.scope value!".to_string(),
);
}
unsafe {
*out = c_str;
}
} else {
unsafe {
*out = std::ptr::null_mut();
}
}
no_error()
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn obo_token_token_type(
token: *mut OboToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
c_str_from_object_string!(token, token_type, out)
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn obo_token_expires_in(
token: *mut OboToken,
out: *mut u32,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let token = unsafe { &mut *token };
unsafe {
*out = token.expires_in;
}
no_error()
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn obo_token_ext_expires_in(
token: *mut OboToken,
out: *mut u32,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let token = unsafe { &mut *token };
unsafe {
*out = token.ext_expires_in;
}
no_error()
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn obo_token_free(input: *mut OboToken) {
free_object!(input);
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn confidential_client_free(input: *mut ConfidentialClientApplication) {
free_object!(input);
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn confidential_acquire_token_silent(
app: *mut ConfidentialClientApplication,
scopes: *const *const c_char,
scopes_len: c_int,
out: *mut *mut ClientToken,
) -> *mut MSAL_ERROR {
if app.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid app parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let app = unsafe { &mut *app };
let scopes_vec = match str_array_to_vec(scopes, scopes_len) {
Ok(s) => s,
Err(e) => return e,
};
let scopes_ref: Vec<&str> = scopes_vec.iter().map(|s| s.as_str()).collect();
match runtime::Runtime::new() {
Ok(rt) => match rt.block_on(async { app.acquire_token_silent(scopes_ref, None).await }) {
Ok(token) => {
unsafe {
*out = Box::into_raw(Box::new(token));
}
no_error()
}
Err(e) => make_error_from_msal_error(e),
},
Err(e) => make_error(MSAL_ERROR_CODE::NO_MEMORY, e.to_string()),
}
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn client_token_access_token(
token: *mut ClientToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
c_str_from_object_string!(token, access_token, out)
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn client_token_token_type(
token: *mut ClientToken,
out: *mut *mut c_char,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
c_str_from_object_string!(token, token_type, out)
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn client_token_expires_in(
token: *mut ClientToken,
out: *mut u32,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let token = unsafe { &*token };
unsafe {
*out = token.expires_in;
}
no_error()
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn client_token_ext_expires_in(
token: *mut ClientToken,
out: *mut u32,
) -> *mut MSAL_ERROR {
if token.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid token parameter!".to_string(),
);
}
if out.is_null() {
return make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Invalid output parameter!".to_string(),
);
}
let token = unsafe { &*token };
unsafe {
*out = token.ext_expires_in;
}
no_error()
}
#[cfg(feature = "on_behalf_of")]
#[no_mangle]
pub unsafe extern "C" fn client_token_free(input: *mut ClientToken) {
free_object!(input);
}
#[cfg(feature = "broker")]
#[no_mangle]
pub unsafe extern "C" fn sealed_data_free(input: *mut SealedData) {
free_object!(input);
}
#[cfg(all(test, feature = "on_behalf_of"))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::error::{ErrorResponse, MsalError};
use std::ffi::{CStr, CString};
fn test_obo_token() -> OboToken {
OboToken {
token_type: "Bearer".to_string(),
scope: None,
expires_in: 3600,
ext_expires_in: 7200,
access_token: "token".to_string(),
refresh_token: None,
}
}
#[test]
fn obo_token_optional_fields_return_null_without_error() {
let mut token = test_obo_token();
let mut scope_out: *mut c_char = std::ptr::null_mut();
let mut refresh_out: *mut c_char = std::ptr::null_mut();
let scope_err = unsafe { obo_token_scope(&mut token, &mut scope_out) };
assert!(scope_err.is_null());
assert!(scope_out.is_null());
let refresh_err = unsafe { obo_token_refresh_token(&mut token, &mut refresh_out) };
assert!(refresh_err.is_null());
assert!(refresh_out.is_null());
}
#[test]
fn obo_token_getters_reject_invalid_pointers() {
let mut out: *mut c_char = std::ptr::null_mut();
let err = unsafe { obo_token_access_token(std::ptr::null_mut(), &mut out) };
assert!(!err.is_null());
assert!(matches!(
unsafe { (*err).code },
MSAL_ERROR_CODE::INVALID_POINTER
));
unsafe {
error_free(err);
}
}
fn assert_invalid_pointer(err: *mut MSAL_ERROR) {
assert!(!err.is_null());
assert!(matches!(
unsafe { (*err).code },
MSAL_ERROR_CODE::INVALID_POINTER
));
unsafe {
error_free(err);
}
}
fn test_confidential_client() -> ConfidentialClientApplication {
let credential = ClientCredential::from_secret("test-secret".to_string());
ConfidentialClientApplication::new(
"test-client-id",
None,
credential,
#[cfg(feature = "set_timeout")]
std::time::Duration::from_secs(3),
#[cfg(feature = "ipvers")]
&[IpVersion::V4, IpVersion::V6],
)
.unwrap()
}
#[test]
fn confidential_acquire_token_on_behalf_of_rejects_invalid_pointers() {
let assertion = CString::new("header.payload.sig").unwrap();
let mut token_out: *mut OboToken = std::ptr::null_mut();
let mut app = test_confidential_client();
assert_invalid_pointer(unsafe {
confidential_acquire_token_on_behalf_of(
std::ptr::null_mut(),
assertion.as_ptr(),
std::ptr::null(),
0,
&mut token_out,
)
});
assert_invalid_pointer(unsafe {
confidential_acquire_token_on_behalf_of(
&mut app,
assertion.as_ptr(),
std::ptr::null(),
0,
std::ptr::null_mut(),
)
});
assert_invalid_pointer(unsafe {
confidential_acquire_token_on_behalf_of(
&mut app,
std::ptr::null(),
std::ptr::null(),
0,
&mut token_out,
)
});
}
#[test]
fn confidential_acquire_token_on_behalf_of_rejects_null_scope_array_with_len() {
let assertion = CString::new("header.payload.sig").unwrap();
let mut app = test_confidential_client();
let mut token_out: *mut OboToken = std::ptr::null_mut();
assert_invalid_pointer(unsafe {
confidential_acquire_token_on_behalf_of(
&mut app,
assertion.as_ptr(),
std::ptr::null(),
1,
&mut token_out,
)
});
}
#[test]
fn confidential_acquire_token_on_behalf_of_rejects_negative_scope_len() {
let assertion = CString::new("header.payload.sig").unwrap();
let mut app = test_confidential_client();
let mut token_out: *mut OboToken = std::ptr::null_mut();
let scopes = [assertion.as_ptr()];
assert_invalid_pointer(unsafe {
confidential_acquire_token_on_behalf_of(
&mut app,
assertion.as_ptr(),
scopes.as_ptr(),
-1,
&mut token_out,
)
});
}
fn test_client_token() -> ClientToken {
ClientToken {
token_type: "Bearer".to_string(),
expires_in: 3600,
ext_expires_in: 7200,
access_token: "eyJ0eXAi...".to_string(),
}
}
#[test]
fn client_token_getters_return_correct_values() {
let mut token = test_client_token();
let mut str_out: *mut c_char = std::ptr::null_mut();
let mut u32_out: u32 = 0;
let err = unsafe { client_token_access_token(&mut token, &mut str_out) };
assert!(err.is_null());
assert!(!str_out.is_null());
let access = unsafe { CStr::from_ptr(str_out) }.to_str().unwrap();
assert_eq!(access, "eyJ0eXAi...");
unsafe {
let _ = CString::from_raw(str_out);
}
str_out = std::ptr::null_mut();
let err = unsafe { client_token_token_type(&mut token, &mut str_out) };
assert!(err.is_null());
assert!(!str_out.is_null());
let tt = unsafe { CStr::from_ptr(str_out) }.to_str().unwrap();
assert_eq!(tt, "Bearer");
unsafe {
let _ = CString::from_raw(str_out);
}
let err = unsafe { client_token_expires_in(&mut token, &mut u32_out) };
assert!(err.is_null());
assert_eq!(u32_out, 3600);
let err = unsafe { client_token_ext_expires_in(&mut token, &mut u32_out) };
assert!(err.is_null());
assert_eq!(u32_out, 7200);
}
#[test]
fn client_token_getters_reject_invalid_pointers() {
let mut out: *mut c_char = std::ptr::null_mut();
assert_invalid_pointer(unsafe {
client_token_access_token(std::ptr::null_mut(), &mut out)
});
assert_invalid_pointer(unsafe { client_token_token_type(std::ptr::null_mut(), &mut out) });
let mut u32_out: u32 = 0;
assert_invalid_pointer(unsafe {
client_token_expires_in(std::ptr::null_mut(), &mut u32_out)
});
assert_invalid_pointer(unsafe {
client_token_ext_expires_in(std::ptr::null_mut(), &mut u32_out)
});
}
#[test]
fn confidential_acquire_token_silent_rejects_invalid_pointers() {
let mut app = test_confidential_client();
let mut token_out: *mut ClientToken = std::ptr::null_mut();
assert_invalid_pointer(unsafe {
confidential_acquire_token_silent(
std::ptr::null_mut(),
std::ptr::null(),
0,
&mut token_out,
)
});
assert_invalid_pointer(unsafe {
confidential_acquire_token_silent(&mut app, std::ptr::null(), 0, std::ptr::null_mut())
});
}
#[test]
fn client_token_free_accepts_valid_pointer() {
let token = Box::into_raw(Box::new(test_client_token()));
unsafe {
client_token_free(token);
}
}
#[test]
fn error_free_handles_obo_claims_and_codes() {
let error = MsalError::OboInteractionRequired {
error: ErrorResponse {
error: "interaction_required".to_string(),
error_description: "AADSTS50076".to_string(),
suberror: Some("basic_action".to_string()),
error_codes: vec![50076, 16000],
},
claims: Some("{\"access_token\":{}}".to_string()),
};
let err = make_error_from_msal_error(error);
assert!(!err.is_null());
assert!(matches!(
unsafe { (*err).code },
MSAL_ERROR_CODE::OBO_INTERACTION_REQUIRED
));
assert!(!unsafe { (*err).claims }.is_null());
assert_eq!(unsafe { (*err).acquire_token_error_codes_len }, 2);
unsafe {
error_free(err);
}
}
}
#[cfg(all(test, feature = "broker"))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod broker_mfa_input_tests {
use super::*;
#[test]
fn broker_mfa_flow_rejects_non_utf8_password() {
let mut client = BrokerClientApplication::new(
None,
None,
None,
None,
#[cfg(feature = "set_timeout")]
std::time::Duration::from_secs(3),
#[cfg(feature = "ipvers")]
&[IpVersion::V4, IpVersion::V6],
)
.unwrap();
let username = CString::new("user@example.com").unwrap();
let invalid_password = [0xff_u8, 0];
let mut flow: *mut MFAAuthContinue = std::ptr::null_mut();
let err = unsafe {
broker_initiate_acquire_token_by_mfa_flow(
&mut client,
username.as_ptr(),
invalid_password.as_ptr().cast(),
std::ptr::null(),
0,
&mut flow,
)
};
assert!(!err.is_null());
assert!(matches!(
unsafe { (*err).code },
MSAL_ERROR_CODE::INVALID_POINTER
));
assert!(flow.is_null());
unsafe {
error_free(err);
}
}
#[test]
fn broker_mfa_flow_for_device_enrollment_rejects_non_utf8_password() {
let mut client = BrokerClientApplication::new(
None,
None,
None,
None,
#[cfg(feature = "set_timeout")]
std::time::Duration::from_secs(3),
#[cfg(feature = "ipvers")]
&[IpVersion::V4, IpVersion::V6],
)
.unwrap();
let username = CString::new("user@example.com").unwrap();
let invalid_password = [0xff_u8, 0];
let mut flow: *mut MFAAuthContinue = std::ptr::null_mut();
let err = unsafe {
broker_initiate_acquire_token_by_mfa_flow_for_device_enrollment(
&mut client,
username.as_ptr(),
invalid_password.as_ptr().cast(),
std::ptr::null(),
0,
&mut flow,
)
};
assert!(!err.is_null());
assert!(matches!(
unsafe { (*err).code },
MSAL_ERROR_CODE::INVALID_POINTER
));
assert!(flow.is_null());
unsafe {
error_free(err);
}
}
}
#[cfg(all(test, feature = "broker", feature = "set_timeout"))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod broker_init_tests {
use super::*;
#[test]
fn broker_init_rejects_null_out() {
let timeout_secs: u64 = 30;
let err = unsafe {
broker_init(
std::ptr::null(),
std::ptr::null(),
std::ptr::null_mut(),
std::ptr::null_mut(),
&timeout_secs,
std::ptr::null_mut(),
)
};
assert!(!err.is_null());
assert!(matches!(
unsafe { (*err).code },
MSAL_ERROR_CODE::INVALID_POINTER
));
unsafe { error_free(err) };
}
#[test]
fn broker_init_with_timeout_constructs_client() {
let timeout_secs: u64 = 30;
let mut out: *mut BrokerClientApplication = std::ptr::null_mut();
let err = unsafe {
broker_init(
std::ptr::null(),
std::ptr::null(),
std::ptr::null_mut(),
std::ptr::null_mut(),
&timeout_secs,
&mut out,
)
};
assert!(err.is_null());
assert!(!out.is_null());
unsafe { broker_free(out) };
}
#[test]
fn broker_init_null_timeout_constructs_client() {
let mut out: *mut BrokerClientApplication = std::ptr::null_mut();
let err = unsafe {
broker_init(
std::ptr::null(),
std::ptr::null(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null(),
&mut out,
)
};
assert!(err.is_null());
assert!(!out.is_null());
unsafe { broker_free(out) };
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod mfa_fido_accessor_tests {
use super::*;
use std::ffi::CStr;
fn fido_flow() -> MFAAuthContinue {
MFAAuthContinue {
fido_challenge: Some("test-challenge".to_string()),
fido_allow_list: Some(vec!["credA".to_string(), "credB".to_string()]),
..Default::default()
}
}
#[test]
fn fido_challenge_returns_value_when_present() {
let mut flow = fido_flow();
let mut out: *mut c_char = std::ptr::null_mut();
let err = unsafe { mfa_auth_continue_fido_challenge(&mut flow, &mut out) };
assert!(err.is_null());
assert!(!out.is_null());
let challenge = unsafe { CStr::from_ptr(out) }.to_str().unwrap();
assert_eq!(challenge, "test-challenge");
unsafe { string_free(out) };
}
#[test]
fn fido_challenge_returns_null_without_error_when_absent() {
let mut flow = MFAAuthContinue::default();
let mut out: *mut c_char = std::ptr::null_mut();
let err = unsafe { mfa_auth_continue_fido_challenge(&mut flow, &mut out) };
assert!(err.is_null());
assert!(out.is_null());
}
#[test]
fn fido_challenge_rejects_invalid_pointers() {
let mut out: *mut c_char = std::ptr::null_mut();
let err = unsafe { mfa_auth_continue_fido_challenge(std::ptr::null_mut(), &mut out) };
assert!(!err.is_null());
assert!(matches!(
unsafe { (*err).code },
MSAL_ERROR_CODE::INVALID_POINTER
));
unsafe { error_free(err) };
let mut flow = fido_flow();
let err = unsafe { mfa_auth_continue_fido_challenge(&mut flow, std::ptr::null_mut()) };
assert!(!err.is_null());
unsafe { error_free(err) };
}
#[test]
fn fido_allow_list_returns_entries_when_present() {
let mut flow = fido_flow();
let mut out: *mut *mut c_char = std::ptr::null_mut();
let mut count: c_int = -1;
let err = unsafe { mfa_auth_continue_fido_allow_list(&mut flow, &mut out, &mut count) };
assert!(err.is_null());
assert_eq!(count, 2);
assert!(!out.is_null());
let entries = unsafe { std::slice::from_raw_parts(out, count as usize) };
let first = unsafe { CStr::from_ptr(entries[0]) }.to_str().unwrap();
let second = unsafe { CStr::from_ptr(entries[1]) }.to_str().unwrap();
assert_eq!(first, "credA");
assert_eq!(second, "credB");
unsafe { mfa_auth_continue_free_fido_allow_list(out, count) };
}
#[test]
fn fido_allow_list_returns_empty_without_error_when_absent() {
let mut flow = MFAAuthContinue::default();
let mut out: *mut *mut c_char = std::ptr::null_mut();
let mut count: c_int = -1;
let err = unsafe { mfa_auth_continue_fido_allow_list(&mut flow, &mut out, &mut count) };
assert!(err.is_null());
assert_eq!(count, 0);
assert!(out.is_null());
unsafe { mfa_auth_continue_free_fido_allow_list(out, count) };
}
}
#[cfg(all(test, feature = "broker"))]
#[allow(clippy::unwrap_used)]
mod ssh_certificate_accessor_tests {
use super::*;
use std::ffi::CStr;
use uuid::Uuid;
#[test]
fn signing_ca_public_key_is_returned_as_an_owned_c_string() {
let mut certificate = EntraSshCertificate {
certificate_body_base64: "certificate".to_string(),
request_key_id: "request-key".to_string(),
certificate_key_id: "object@tenant".to_string(),
serial: 1,
principals: vec!["user@example.com".to_string()],
tenant_id: Uuid::nil(),
object_id: Uuid::nil(),
display_name: None,
valid_after: 1,
valid_before: 2,
expires_in: 1,
ext_expires_in: 1,
scope: None,
signing_ca_openssh_public_key: "ssh-rsa AAAA".to_string(),
signing_ca_fingerprint_sha256: "SHA256:test".to_string(),
};
let mut out = std::ptr::null_mut();
let error = unsafe { ssh_certificate_ca_openssh_public_key(&mut certificate, &mut out) };
assert!(error.is_null());
assert_eq!(
unsafe { CStr::from_ptr(out) }.to_str().unwrap(),
"ssh-rsa AAAA"
);
unsafe { string_free(out) };
}
}