use crate::error::MsalError;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::slice;
use tracing::error;
#[allow(dead_code)]
pub(crate) fn wrap_c_char(input: *const c_char) -> Option<String> {
if input.is_null() {
return None;
}
let c_str = unsafe { CStr::from_ptr(input) };
match c_str.to_str() {
Ok(output) => Some(output.to_string()),
Err(_) => None,
}
}
pub(crate) fn wrap_string(input: &str) -> *mut c_char {
match CString::new(input.to_string()) {
Ok(msg) => msg.into_raw(),
Err(e) => {
error!("{:?}", e);
ptr::null_mut()
}
}
}
macro_rules! free_object {
($input:ident) => {{
if !$input.is_null() {
unsafe {
let _ = Box::from_raw($input);
}
}
}};
}
#[allow(unused_macros)]
macro_rules! run_async {
($client:ident, $func:ident $(, $arg:expr)* $(,)?) => {{
match runtime::Runtime::new() {
Ok(rt) => rt.block_on(async {
match $client.$func($($arg),*).await {
Ok(resp) => Ok(resp),
Err(e) => Err(make_error_from_msal_error(e)),
}
}),
Err(e) => {
Err(make_error(MSAL_ERROR_CODE::NO_MEMORY, e.to_string()))
}
}
}}
}
#[allow(dead_code)]
pub(crate) fn str_array_to_vec(
arr: *const *const c_char,
len: c_int,
) -> Result<Vec<String>, *mut MSAL_ERROR> {
if len < 0 {
return Err(make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Negative array length is invalid".to_string(),
));
}
if arr.is_null() {
if len == 0 {
return Ok(vec![]);
}
return Err(make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Null array pointer with non-zero length".to_string(),
));
}
if len == 0 {
return Ok(vec![]);
}
let slice = unsafe { slice::from_raw_parts(arr, len as usize) };
let mut array = Vec::new();
for &item in slice {
if item.is_null() {
return Err(make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
"Null string pointer in input array".to_string(),
));
}
let c_item = unsafe { CStr::from_ptr(item) };
let str_item = match c_item.to_str() {
Ok(str_item) => str_item,
Err(e) => {
return Err(make_error(MSAL_ERROR_CODE::INVALID_POINTER, e.to_string()));
}
};
array.push(str_item.to_string());
}
Ok(array)
}
#[allow(unused_macros)]
macro_rules! str_vec_ref {
($items:ident) => {
$items.iter().map(|i| i.as_str()).collect()
};
}
macro_rules! c_str_from_object_string {
($obj:ident, $item:ident, $out:ident) => {{
let obj = unsafe { &mut *$obj };
let c_str = wrap_string(&obj.$item);
if !c_str.is_null() {
unsafe {
*$out = c_str;
}
no_error()
} else {
make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
format!("Invalid object {}.{}", stringify!($obj), stringify!($item)),
)
}
}};
}
macro_rules! c_str_from_object_option_string {
($obj:ident, $item:ident, $out:ident) => {{
let obj = unsafe { &mut *$obj };
match &obj.$item {
Some(item) => {
let c_str = wrap_string(&item);
if !c_str.is_null() {
unsafe {
*$out = c_str;
}
no_error()
} else {
make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
format!("Invalid object {}.{}", stringify!($obj), stringify!($item)),
)
}
}
None => make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
format!("Object is None {}.{}", stringify!($obj), stringify!($item)),
),
}
}};
}
macro_rules! c_str_from_object_func {
($obj:ident, $func:ident, $out:ident $(, $arg:expr)* $(,)?) => {{
let obj = unsafe { &mut *$obj };
match obj.$func($($arg),*) {
Ok(item) => {
let c_str = wrap_string(&item);
if !c_str.is_null() {
unsafe {
*$out = c_str;
}
no_error()
} else {
make_error(
MSAL_ERROR_CODE::INVALID_POINTER,
format!("Invalid response {}.{}()", stringify!($obj), stringify!($func))
)
}
}
Err(e) => {
make_error(MSAL_ERROR_CODE::INVALID_POINTER, e.to_string())
}
}
}};
}
#[repr(C)]
#[derive(Copy, Clone, PartialEq)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum MSAL_ERROR_CODE {
INVALID_JSON,
INVALID_BASE64,
INVALID_REGEX,
INVALID_PARSE,
ACQUIRE_TOKEN_FAILED,
GENERAL_FAILURE,
REQUEST_FAILED,
AUTH_TYPE_UNSUPPORTED,
TPM_FAIL,
URL_FORMAT_FAILED,
DEVICE_ENROLLMENT_FAIL,
CRYPTO_FAIL,
NOT_IMPLEMENTED,
CONFIG_ERROR,
MFA_POLL_CONTINUE,
MISSING,
FORMAT_ERROR,
INVALID_POINTER,
NO_MEMORY,
AADSTS_ERROR,
#[cfg(feature = "changepassword")]
CHANGE_PASSWORD,
PASSWORD_REQUIRED,
SKIP_MFA_REGISTRATION,
CONSENT_REQUESTED,
AUTH_CODE_RECEIVED,
MFA_REQUIRED,
AUTHORIZATION_DENIED,
MFA_INVALID_CODE,
MFA_DAG_FALLBACK_DISABLED,
#[cfg(feature = "on_behalf_of")]
OBO_INTERACTION_REQUIRED,
}
#[repr(C)]
#[allow(non_camel_case_types)]
pub struct MSAL_ERROR {
pub code: MSAL_ERROR_CODE,
pub msg: *const c_char,
pub claims: *const c_char,
pub aadsts_code: u32,
pub acquire_token_error_codes: *mut u32,
pub acquire_token_error_codes_len: usize,
}
impl From<MsalError> for MSAL_ERROR_CODE {
fn from(error: MsalError) -> Self {
match error {
MsalError::InvalidJson(_) => MSAL_ERROR_CODE::INVALID_JSON,
MsalError::InvalidBase64(_) => MSAL_ERROR_CODE::INVALID_BASE64,
MsalError::InvalidRegex(_) => MSAL_ERROR_CODE::INVALID_REGEX,
MsalError::InvalidParse(_) => MSAL_ERROR_CODE::INVALID_PARSE,
MsalError::AcquireTokenFailed(_) => MSAL_ERROR_CODE::ACQUIRE_TOKEN_FAILED,
MsalError::GeneralFailure(_) => MSAL_ERROR_CODE::GENERAL_FAILURE,
MsalError::RequestFailed(_) => MSAL_ERROR_CODE::REQUEST_FAILED,
MsalError::AuthTypeUnsupported => MSAL_ERROR_CODE::AUTH_TYPE_UNSUPPORTED,
MsalError::TPMFail(_) => MSAL_ERROR_CODE::TPM_FAIL,
MsalError::URLFormatFailed(_) => MSAL_ERROR_CODE::URL_FORMAT_FAILED,
MsalError::DeviceEnrollmentFail(_) => MSAL_ERROR_CODE::DEVICE_ENROLLMENT_FAIL,
MsalError::CryptoFail(_) => MSAL_ERROR_CODE::CRYPTO_FAIL,
MsalError::NotImplemented => MSAL_ERROR_CODE::NOT_IMPLEMENTED,
MsalError::ConfigError(_) => MSAL_ERROR_CODE::CONFIG_ERROR,
MsalError::MFAPollContinue => MSAL_ERROR_CODE::MFA_POLL_CONTINUE,
MsalError::AADSTSError(_) => MSAL_ERROR_CODE::AADSTS_ERROR,
MsalError::Missing(_) => MSAL_ERROR_CODE::MISSING,
MsalError::FormatError(_) => MSAL_ERROR_CODE::FORMAT_ERROR,
#[cfg(feature = "changepassword")]
MsalError::ChangePassword => MSAL_ERROR_CODE::CHANGE_PASSWORD,
MsalError::PasswordRequired => MSAL_ERROR_CODE::PASSWORD_REQUIRED,
MsalError::SkipMfaRegistration(_, _, _) => MSAL_ERROR_CODE::SKIP_MFA_REGISTRATION,
MsalError::ConsentRequested(_) => MSAL_ERROR_CODE::CONSENT_REQUESTED,
MsalError::AuthCodeReceived(_) => MSAL_ERROR_CODE::AUTH_CODE_RECEIVED,
MsalError::MFARequired => MSAL_ERROR_CODE::MFA_REQUIRED,
MsalError::AuthorizationDenied => MSAL_ERROR_CODE::AUTHORIZATION_DENIED,
MsalError::MFAInvalidCode(_) => MSAL_ERROR_CODE::MFA_INVALID_CODE,
MsalError::MFADAGFallbackDisabled => MSAL_ERROR_CODE::MFA_DAG_FALLBACK_DISABLED,
#[cfg(feature = "on_behalf_of")]
MsalError::OboInteractionRequired { .. } => MSAL_ERROR_CODE::OBO_INTERACTION_REQUIRED,
}
}
}
impl From<MsalError> for MSAL_ERROR {
fn from(error: MsalError) -> Self {
let aadsts_code = match &error {
MsalError::AADSTSError(ref err) => err.code,
_ => 0,
};
let acquire_token_error_codes = match &error {
MsalError::AcquireTokenFailed(ref err) => err.error_codes.clone(),
#[cfg(feature = "on_behalf_of")]
MsalError::OboInteractionRequired { ref error, .. } => error.error_codes.clone(),
_ => vec![],
};
#[cfg(feature = "on_behalf_of")]
let claims = match &error {
MsalError::OboInteractionRequired {
claims: Some(claims),
..
} => match CString::new(claims.clone()) {
Ok(cstr) => cstr.into_raw() as *const c_char,
Err(_) => std::ptr::null(),
},
_ => std::ptr::null(),
};
#[cfg(not(feature = "on_behalf_of"))]
let claims = std::ptr::null();
let msg = match CString::new(error.to_string()) {
Ok(cstr) => cstr.into_raw(),
Err(_) => std::ptr::null(),
};
let code = MSAL_ERROR_CODE::from(error);
let mut acquire_token_error_codes = acquire_token_error_codes.into_boxed_slice();
let acquire_token_error_codes_len = acquire_token_error_codes.len();
let acquire_token_error_codes_ptr = if acquire_token_error_codes_len == 0 {
std::ptr::null_mut()
} else {
acquire_token_error_codes.as_mut_ptr()
};
std::mem::forget(acquire_token_error_codes);
MSAL_ERROR {
code,
msg,
claims,
aadsts_code,
acquire_token_error_codes: acquire_token_error_codes_ptr,
acquire_token_error_codes_len,
}
}
}
pub fn no_error() -> *mut MSAL_ERROR {
std::ptr::null_mut()
}
pub fn make_error(code: MSAL_ERROR_CODE, msg: String) -> *mut MSAL_ERROR {
let msg = match CString::new(msg) {
Ok(cstr) => cstr.into_raw(),
Err(_) => std::ptr::null(),
};
Box::into_raw(Box::new(MSAL_ERROR {
code,
msg,
claims: std::ptr::null(),
aadsts_code: 0,
acquire_token_error_codes: std::ptr::null_mut(),
acquire_token_error_codes_len: 0,
}))
}
pub fn make_error_from_msal_error(error: MsalError) -> *mut MSAL_ERROR {
Box::into_raw(Box::new(MSAL_ERROR::from(error)))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::error::ErrorResponse;
fn free_msal_error_fields(mut error: MSAL_ERROR) {
unsafe {
if !error.msg.is_null() {
drop(CString::from_raw(error.msg as *mut c_char));
error.msg = std::ptr::null();
}
if !error.claims.is_null() {
drop(CString::from_raw(error.claims as *mut c_char));
error.claims = std::ptr::null();
}
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);
error.acquire_token_error_codes = std::ptr::null_mut();
}
}
}
#[test]
fn str_array_to_vec_rejects_negative_len() {
let res = str_array_to_vec(std::ptr::null(), -1);
assert!(res.is_err());
if let Err(error) = res {
unsafe {
let error = Box::from_raw(error);
if !error.msg.is_null() {
drop(CString::from_raw(error.msg as *mut c_char));
}
}
}
}
#[test]
fn str_array_to_vec_rejects_null_pointer_with_non_zero_len() {
let res = str_array_to_vec(std::ptr::null(), 1);
assert!(res.is_err());
if let Err(error) = res {
unsafe {
let error = Box::from_raw(error);
if !error.msg.is_null() {
drop(CString::from_raw(error.msg as *mut c_char));
}
}
}
}
#[test]
fn str_array_to_vec_accepts_empty_null_input() {
let res = str_array_to_vec(std::ptr::null(), 0).unwrap();
assert!(res.is_empty());
}
#[test]
fn str_array_to_vec_parses_valid_input() {
let item_a = CString::new("scope.a").unwrap();
let item_b = CString::new("scope.b").unwrap();
let input = [item_a.as_ptr(), item_b.as_ptr()];
let res = str_array_to_vec(input.as_ptr(), input.len() as c_int).unwrap();
assert_eq!(res, vec!["scope.a".to_string(), "scope.b".to_string()]);
}
#[test]
fn msal_error_from_acquire_token_failed_preserves_error_codes() {
let error = MsalError::AcquireTokenFailed(ErrorResponse {
error: "invalid_grant".to_string(),
error_description: "AADSTS65001".to_string(),
suberror: None,
error_codes: vec![65001, 50076],
});
let c_error = MSAL_ERROR::from(error);
assert_eq!(c_error.acquire_token_error_codes_len, 2);
let codes = unsafe {
std::slice::from_raw_parts(
c_error.acquire_token_error_codes,
c_error.acquire_token_error_codes_len,
)
};
assert_eq!(codes, &[65001, 50076]);
free_msal_error_fields(c_error);
}
#[cfg(feature = "on_behalf_of")]
#[test]
fn msal_error_from_obo_interaction_required_sets_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],
},
claims: Some("{\"access_token\":{}}".to_string()),
};
let c_error = MSAL_ERROR::from(error);
assert!(matches!(
c_error.code,
MSAL_ERROR_CODE::OBO_INTERACTION_REQUIRED
));
assert!(!c_error.claims.is_null());
let claims = unsafe { CStr::from_ptr(c_error.claims) }
.to_str()
.unwrap()
.to_string();
assert_eq!(claims, "{\"access_token\":{}}");
assert_eq!(c_error.acquire_token_error_codes_len, 1);
let codes = unsafe {
std::slice::from_raw_parts(
c_error.acquire_token_error_codes,
c_error.acquire_token_error_codes_len,
)
};
assert_eq!(codes, &[50076]);
free_msal_error_fields(c_error);
}
}