use std::{
os::raw::{c_int, c_uint, c_void},
pin::Pin,
sync::{Arc, Mutex, RwLock},
time::Duration,
};
use ffi_sdk::{BoxedAuthClient, BoxedLoginProvider, FsComponent};
use crate::{
ditto::{Ditto, DittoFields},
error::{DittoError, ErrorKind},
transport::TransportSync,
utils::prelude::*,
};
pub trait DittoAuthenticationEventHandler: Send + Sync {
fn authentication_required(&self, auth: DittoAuthenticator);
fn authentication_expiring_soon(&self, auth: DittoAuthenticator, seconds_remaining: Duration);
}
#[derive(Clone, Debug)]
pub struct AuthenticationClientFeedback {
pub feedback: Option<serde_json::Value>,
}
pub(crate) struct LoginProvider {
pub(crate) _provider: BoxedLoginProvider,
pub(crate) ctx: Arc<Mutex<LoginProviderCtx>>,
}
pub(crate) struct LoginProviderCtx {
auth_event_handler: Pin<Box<dyn DittoAuthenticationEventHandler + 'static>>,
authenticator: Option<DittoAuthenticator>,
#[allow(dead_code)] cached_expiry_time: Option<u32>,
}
impl LoginProvider {
pub fn new(handler: impl DittoAuthenticationEventHandler + 'static) -> Self {
let ctx = LoginProviderCtx {
auth_event_handler: Box::pin(handler),
authenticator: None,
cached_expiry_time: None,
};
let arc_ctx = Arc::new(Mutex::new(ctx));
let raw_context = Arc::as_ptr(&arc_ctx) as *mut c_void;
let c_provider = unsafe {
ffi_sdk::ditto_auth_client_make_login_provider(
raw_context,
Some(LoginProviderCtx::retain),
Some(LoginProviderCtx::release),
Some(LoginProviderCtx::authentication_expiring),
)
};
LoginProvider {
_provider: c_provider,
ctx: arc_ctx,
}
}
}
impl LoginProviderCtx {
pub(crate) extern "C" fn retain(ctx: *mut c_void) {
let ptr = ctx.cast::<Mutex<LoginProviderCtx>>();
unsafe { Arc::increment_strong_count(ptr) }; }
pub(crate) extern "C" fn release(ctx: *mut c_void) {
let ptr = ctx.cast::<Mutex<LoginProviderCtx>>();
unsafe {
Arc::decrement_strong_count(ptr);
} }
pub(crate) extern "C" fn authentication_expiring(ctx: *mut c_void, seconds_remaining: c_uint) {
let ctx_ptr: *const Mutex<LoginProviderCtx> = ctx.cast();
let arc_ctx: &Mutex<LoginProviderCtx> = unsafe { &*ctx_ptr }; let mut ctx_ref = arc_ctx.lock().expect("LoginProvider Mutex is poisoned"); match &ctx_ref.authenticator {
Some(authn) => {
if seconds_remaining == 0 {
ctx_ref
.auth_event_handler
.authentication_required(authn.retain());
} else {
let duration = Duration::from_secs(seconds_remaining.into());
ctx_ref
.auth_event_handler
.authentication_expiring_soon(authn.retain(), duration);
}
}
None => ctx_ref.cached_expiry_time = Some(seconds_remaining),
}
}
pub(crate) fn set_authenticator(&mut self, authenticator: DittoAuthenticator) {
self.authenticator = Some(authenticator);
if let Some(authn) = &self.authenticator {
if let Some(time) = self.cached_expiry_time {
if time == 0 {
self.auth_event_handler
.authentication_required(authn.retain());
} else {
let duration = Duration::from_secs(time.into());
self.auth_event_handler
.authentication_expiring_soon(authn.retain(), duration);
}
self.cached_expiry_time = None; }
}
}
}
pub struct ValidityListener(Arc<RwLock<TransportSync>>);
impl ValidityListener {
pub fn new(
ditto: Arc<RwLock<TransportSync>>,
auth_client_ref: impl AsRef<BoxedAuthClient>,
) -> Arc<ValidityListener> {
let this = Arc::new(ValidityListener(ditto));
let this_ptr = Arc::as_ptr(&this) as *mut c_void;
extern "C" fn on_validity_update(ctx: *mut c_void, web_valid: c_int, x509_valid: c_int) {
let ctx_ref: &ValidityListener =
unsafe { ctx.cast::<ValidityListener>().as_ref().expect("Got Null") }; {
let mut lock = ctx_ref.0.write().expect("Poisoned Transport Lock");
lock.validity_updated(web_valid != 0, x509_valid != 0);
}
}
extern "C" fn retain(ctx: *mut c_void) {
let ptr = ctx.cast::<ValidityListener>();
unsafe {
Arc::increment_strong_count(ptr);
}
}
extern "C" fn release(ctx: *mut c_void) {
let ptr = ctx.cast::<ValidityListener>();
unsafe {
Arc::decrement_strong_count(ptr);
}
}
unsafe {
ffi_sdk::ditto_auth_client_set_validity_listener(
auth_client_ref.as_ref(),
this_ptr,
Some(retain),
Some(release),
Some(on_validity_update),
);
}
this
}
}
impl RefCounted for DittoAuthenticator {}
#[derive(Clone)]
pub struct DittoAuthenticator {
auth_client: Arc<BoxedAuthClient>,
pub(crate) ditto_fields: std::sync::Weak<DittoFields>,
}
impl DittoAuthenticator {
pub fn new(auth_client: Arc<BoxedAuthClient>) -> Self {
DittoAuthenticator {
auth_client,
ditto_fields: std::sync::Weak::<DittoFields>::new(),
}
}
pub(crate) fn auth_client(&self) -> Arc<BoxedAuthClient> {
self.auth_client.retain()
}
pub fn login_with_token_and_feedback(
&self,
token: &str,
provider: &str,
) -> Result<AuthenticationClientFeedback, DittoError> {
let c_token = char_p::new(token);
let c_provider = char_p::new(provider);
let status = unsafe {
ffi_sdk::ditto_auth_client_login_with_token_and_feedback(
&*self.auth_client,
c_token.as_ref(),
c_provider.as_ref(),
)
};
fn parse_client_info(s: Option<safer_ffi::String>) -> AuthenticationClientFeedback {
AuthenticationClientFeedback {
feedback: s.map(|x| serde_json::from_str(&x).unwrap()),
}
}
match status.return_code {
0 => Ok(parse_client_info(status.client_info)),
_ => Err(DittoError::from_authentication_feedback(parse_client_info(
status.client_info,
))),
}
}
pub fn logout<R>(&self, cleanup: impl FnOnce(Ditto) -> R) -> Result<R, DittoError> {
let fields = self
.ditto_fields
.upgrade()
.expect("fields hasn't been dropped yet");
let ditto = Ditto::new_with_fields(fields);
let status = unsafe { ffi_sdk::ditto_auth_client_logout(&*self.auth_client) };
if status != 0 {
return Err(DittoError::from_ffi(ErrorKind::Authentication));
}
ditto.stop_sync();
let ret = cleanup(ditto);
Ok(ret)
}
pub fn is_authenticated(&self) -> bool {
unsafe { ffi_sdk::ditto_auth_client_is_web_valid(&*self.auth_client) != 0 }
}
pub fn user_id(&self) -> Option<String> {
unsafe { ffi_sdk::ditto_auth_client_user_id(&*self.auth_client) }.map(|c_msg| {
let s = c_msg.to_str().to_owned();
unsafe { ffi_sdk::ditto_c_string_free(c_msg) };
s
})
}
}
impl DiskUser for DittoAuthenticator {
fn ditto_component() -> FsComponent {
FsComponent::Auth
}
fn ditto(&self) -> Arc<BoxedDitto> {
let fields = self
.ditto_fields
.upgrade()
.expect("fields hasn't been dropped yet");
fields.ditto.retain()
}
}