use crate::{msal, AccountInfo};
use msal::{JsArrayString, JsHashMapStrStr};
use std::{borrow::Cow, collections::HashMap};
#[derive(Clone)]
pub enum ResponseMode {
Query,
Fragment,
FormPost,
}
impl ResponseMode {
fn as_str(&self) -> &str {
match &self {
ResponseMode::Query => "query",
ResponseMode::Fragment => "fragment",
ResponseMode::FormPost => "form_post",
}
}
}
#[derive(Clone)]
pub struct BaseAuthRequest<'a> {
scopes: Vec<Cow<'a, str>>,
authority: Option<Cow<'a, str>>,
correlation_id: Option<Cow<'a, str>>,
}
struct IterBaseAuthRequest<'a, T> {
base_auth_request: &'a BaseAuthRequest<'a>,
destination: &'a T,
authority: &'a dyn Fn(&T, &Cow<'a, str>),
correlation_id: &'a dyn Fn(&T, &Cow<'a, str>),
}
impl<'a, T> IterBaseAuthRequest<'a, T> {
fn iter_all(self) {
if let Some(v) = &self.base_auth_request.authority {
(self.authority)(self.destination, v)
}
if let Some(v) = &self.base_auth_request.correlation_id {
(self.correlation_id)(self.destination, v)
}
}
}
pub trait SetBaseAuthRequest<'a> {
fn base_request(&mut self) -> &mut BaseAuthRequest<'a>;
fn set_authority<T>(mut self, authority: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.base_request().authority = Some(authority.into());
self
}
fn set_correlation_id<T>(mut self, correlation_id: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.base_request().correlation_id = Some(correlation_id.into());
self
}
}
impl<'a> BaseAuthRequest<'a> {
fn new<T>(scopes: &'a [T]) -> Self
where
T: Clone + Into<Cow<'a, str>>,
{
Self {
scopes: scopes.iter().cloned().map(Into::into).collect(),
authority: None,
correlation_id: None,
}
}
}
#[derive(Clone)]
pub enum Prompt {
Login,
None,
Consent,
SelectAccount,
}
impl Prompt {
fn as_str(&self) -> &str {
match &self {
Prompt::Login => "login",
Prompt::None => "none",
Prompt::Consent => "consent",
Prompt::SelectAccount => "select_account",
}
}
}
#[derive(Clone)]
pub struct AuthorizationUrlRequest<'a> {
base_request: BaseAuthRequest<'a>,
redirect_uri: Option<Cow<'a, str>>,
extra_scopes_to_consent: Option<Vec<Cow<'a, str>>>,
response_mode: Option<ResponseMode>,
code_challenge: Option<Cow<'a, str>>,
code_challenge_method: Option<Cow<'a, str>>,
state: Option<Cow<'a, str>>,
prompt: Option<Prompt>,
login_hint: Option<Cow<'a, str>>,
domain_hint: Option<Cow<'a, str>>,
extra_query_parameters: Option<HashMap<Cow<'a, str>, Cow<'a, str>>>,
claims: Option<Cow<'a, str>>,
nonce: Option<Cow<'a, str>>,
}
impl<'a> AuthorizationUrlRequest<'a> {
pub fn new<T>(scopes: &'a [T]) -> Self
where
T: Clone + Into<Cow<'a, str>>,
{
Self {
base_request: BaseAuthRequest::new(scopes),
redirect_uri: None,
extra_scopes_to_consent: None,
response_mode: None,
code_challenge: None,
code_challenge_method: None,
state: None,
prompt: None,
login_hint: None,
domain_hint: None,
extra_query_parameters: None,
claims: None,
nonce: None,
}
}
pub fn empty() -> Self {
let base_request = BaseAuthRequest {
scopes: Vec::new(),
authority: None,
correlation_id: None,
};
Self {
base_request,
redirect_uri: None,
extra_scopes_to_consent: None,
response_mode: None,
code_challenge: None,
code_challenge_method: None,
state: None,
prompt: None,
login_hint: None,
domain_hint: None,
extra_query_parameters: None,
claims: None,
nonce: None,
}
}
}
struct IterAuthorizationUrlRequest<'a, T> {
auth_url_request: &'a AuthorizationUrlRequest<'a>,
destination: &'a T,
base_request: IterBaseAuthRequest<'a, T>,
redirect_uri: &'a dyn Fn(&T, &Cow<'a, str>),
extra_scopes_to_consent: &'a dyn Fn(&T, &Vec<Cow<'a, str>>),
response_mode: &'a dyn Fn(&T, &ResponseMode),
code_challenge: &'a dyn Fn(&T, &Cow<'a, str>),
code_challenge_method: &'a dyn Fn(&T, &Cow<'a, str>),
state: &'a dyn Fn(&T, &Cow<'a, str>),
prompt: &'a dyn Fn(&T, &'a str),
login_hint: &'a dyn Fn(&T, &Cow<'a, str>),
domain_hint: &'a dyn Fn(&T, &Cow<'a, str>),
extra_query_parameters: &'a dyn Fn(&T, &HashMap<Cow<'a, str>, Cow<'a, str>>),
claims: &'a dyn Fn(&T, &Cow<'a, str>),
nonce: &'a dyn Fn(&T, &Cow<'a, str>),
}
impl<'a, T> IterAuthorizationUrlRequest<'a, T> {
fn iter_all(self) {
self.base_request.iter_all();
if let Some(v) = &self.auth_url_request.redirect_uri {
(self.redirect_uri)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.extra_scopes_to_consent {
(self.extra_scopes_to_consent)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.response_mode {
(self.response_mode)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.code_challenge {
(self.code_challenge)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.code_challenge_method {
(self.code_challenge_method)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.state {
(self.state)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.prompt {
(self.prompt)(self.destination, v.as_str())
}
if let Some(v) = &self.auth_url_request.login_hint {
(self.login_hint)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.domain_hint {
(self.domain_hint)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.extra_query_parameters {
(self.extra_query_parameters)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.claims {
(self.claims)(self.destination, v)
}
if let Some(v) = &self.auth_url_request.nonce {
(self.nonce)(self.destination, v)
}
}
}
pub trait SetAuthorizationUrlRequest<'a> {
fn auth_request(&mut self) -> &mut AuthorizationUrlRequest<'a>;
fn set_redirect_uri<T>(mut self, redirect_uri: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().redirect_uri = Some(redirect_uri.into());
self
}
fn set_extra_scopes_to_consent<T>(mut self, extra_scopes_to_consent: &'a [T]) -> Self
where
T: Into<Cow<'a, str>> + Clone,
Self: std::marker::Sized,
{
self.auth_request().extra_scopes_to_consent = Some(
extra_scopes_to_consent
.iter()
.cloned()
.map(|v| v.into())
.collect(),
);
self
}
fn set_response_mode(mut self, response_mode: ResponseMode) -> Self
where
Self: std::marker::Sized,
{
self.auth_request().response_mode = Some(response_mode);
self
}
fn set_code_challenge<T>(mut self, code_challenge: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().code_challenge = Some(code_challenge.into());
self
}
fn set_code_challenge_method<T>(mut self, code_challenge_method: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().code_challenge_method = Some(code_challenge_method.into());
self
}
fn set_state<T>(mut self, state: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().state = Some(state.into());
self
}
fn set_prompt<T>(mut self, prompt: Prompt) -> Self
where
Self: std::marker::Sized,
{
self.auth_request().prompt = Some(prompt);
self
}
fn set_login_hint<T>(mut self, login_hint: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().login_hint = Some(login_hint.into());
self
}
fn set_domain_hint<T>(mut self, domain_hint: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().domain_hint = Some(domain_hint.into());
self
}
fn set_extra_query_parameters<T>(mut self, extra_query_parameters: HashMap<T, T>) -> Self
where
T: Into<Cow<'a, str>> + Clone,
Self: std::marker::Sized,
{
let mut hm: HashMap<Cow<'a, str>, Cow<'a, str>> = HashMap::new();
for (k, v) in extra_query_parameters {
hm.insert(k.into(), v.into());
}
self.auth_request().extra_query_parameters = Some(hm);
self
}
fn set_claims<T>(mut self, claims: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().claims = Some(claims.into());
self
}
fn set_nonce<T>(mut self, nonce: T) -> Self
where
T: Into<Cow<'a, str>>,
Self: std::marker::Sized,
{
self.auth_request().nonce = Some(nonce.into());
self
}
}
impl<'a> SetAuthorizationUrlRequest<'a> for AuthorizationUrlRequest<'a> {
fn auth_request(&mut self) -> &mut AuthorizationUrlRequest<'a> {
self
}
}
impl<'a> SetBaseAuthRequest<'a> for AuthorizationUrlRequest<'a> {
fn base_request(&mut self) -> &mut BaseAuthRequest<'a> {
&mut self.base_request
}
}
impl<'a> From<&'a AuthorizationUrlRequest<'a>> for msal::AuthorizationUrlRequest {
fn from(request: &'a AuthorizationUrlRequest<'a>) -> Self {
let js = msal::AuthorizationUrlRequest::new(
&JsArrayString::from(&request.base_request.scopes).into(),
);
IterAuthorizationUrlRequest {
auth_url_request: request,
destination: &js,
base_request: IterBaseAuthRequest {
base_auth_request: &request.base_request,
destination: &js,
authority: &|js, v| js.set_authority(v),
correlation_id: &|js, v| js.set_correlation_id(v),
},
redirect_uri: &|js, v| js.set_redirect_uri(v),
extra_scopes_to_consent: &|js, v| {
js.set_extra_scopes_to_consent(JsArrayString::from(v).into())
},
response_mode: &|js, v| js.set_response_mode(v.as_str()),
code_challenge: &|js, v| js.set_code_challenge(v),
code_challenge_method: &|js, v| js.set_code_challenge_method(v),
state: &|js, v| js.set_state(v),
prompt: &|js, v| js.set_prompt(v),
login_hint: &|js, v| js.set_login_hint(v),
domain_hint: &|js, v| js.set_domain_hint(v),
extra_query_parameters: &|js, v| {
js.set_extra_query_parameters(JsHashMapStrStr::from(v).into())
},
claims: &|js, v| js.set_claims(v),
nonce: &|js, v| js.set_nonce(v),
}
.iter_all();
js
}
}
#[cfg(feature = "redirect")]
#[derive(Clone)]
pub struct RedirectRequest<'a> {
auth_url_req: AuthorizationUrlRequest<'a>,
redirect_start_page: Option<Cow<'a, str>>,
}
#[cfg(feature = "redirect")]
impl<'a> RedirectRequest<'a> {
pub fn new<T>(scopes: &'a [T]) -> Self
where
T: Clone + Into<Cow<'a, str>>,
{
Self {
auth_url_req: AuthorizationUrlRequest::new(scopes),
redirect_start_page: None,
}
}
}
#[cfg(feature = "redirect")]
impl<'a> SetBaseAuthRequest<'a> for RedirectRequest<'a> {
fn base_request(&mut self) -> &mut BaseAuthRequest<'a> {
&mut self.auth_url_req.base_request
}
}
#[cfg(feature = "redirect")]
impl<'a> SetAuthorizationUrlRequest<'a> for RedirectRequest<'a> {
fn auth_request(&mut self) -> &mut AuthorizationUrlRequest<'a> {
&mut self.auth_url_req
}
}
#[cfg(feature = "redirect")]
impl<'a> From<&'a RedirectRequest<'a>> for msal::RedirectRequest {
fn from(request: &'a RedirectRequest<'a>) -> Self {
let js = msal::RedirectRequest::new(
&JsArrayString::from(&request.auth_url_req.base_request.scopes).into(),
);
IterAuthorizationUrlRequest {
auth_url_request: &request.auth_url_req,
destination: &js,
base_request: IterBaseAuthRequest {
base_auth_request: &request.auth_url_req.base_request,
destination: &js,
authority: &|js, v| js.set_authority(v),
correlation_id: &|js, v| js.set_correlation_id(v),
},
redirect_uri: &|js, v| js.set_redirect_uri(v),
extra_scopes_to_consent: &|js, v| {
js.set_extra_scopes_to_consent(JsArrayString::from(v).into())
},
response_mode: &|js, v| js.set_response_mode(v.as_str()),
code_challenge: &|js, v| js.set_code_challenge(v),
code_challenge_method: &|js, v| js.set_code_challenge_method(v),
state: &|js, v| js.set_state(v),
prompt: &|js, v| js.set_prompt(v),
login_hint: &|js, v| js.set_login_hint(v),
domain_hint: &|js, v| js.set_domain_hint(v),
extra_query_parameters: &|js, v| {
js.set_extra_query_parameters(JsHashMapStrStr::from(v).into())
},
claims: &|js, v| js.set_claims(v),
nonce: &|js, v| js.set_nonce(v),
}
.iter_all();
if let Some(v) = &request.redirect_start_page {
js.set_redirect_start_page(v)
}
js
}
}
#[derive(Clone)]
pub struct SilentRequest<'a> {
base_request: BaseAuthRequest<'a>,
account: &'a AccountInfo,
force_refresh: Option<bool>,
redirect_uri: Option<Cow<'a, str>>,
}
impl<'a> SetBaseAuthRequest<'a> for SilentRequest<'a> {
fn base_request(&mut self) -> &mut BaseAuthRequest<'a> {
&mut self.base_request
}
}
impl<'a> SilentRequest<'a> {
pub fn new<T>(scopes: &'a [T], account_info: &'a AccountInfo) -> Self
where
T: Clone + Into<Cow<'a, str>>,
{
Self {
base_request: BaseAuthRequest::new(scopes),
account: account_info,
force_refresh: None,
redirect_uri: None,
}
}
pub fn set_force_refresh(mut self, force_refresh: bool) -> Self {
self.force_refresh = Some(force_refresh);
self
}
pub fn set_redirect_uri<T>(mut self, redirect_uri: T) -> Self
where
T: Into<Cow<'a, str>>,
{
self.redirect_uri = Some(redirect_uri.into());
self
}
}
impl<'a> From<&'a SilentRequest<'a>> for msal::SilentRequest {
fn from(request: &'a SilentRequest) -> Self {
let js = msal::SilentRequest::new(
&JsArrayString::from(&request.base_request.scopes).into(),
request.account.into(),
);
IterBaseAuthRequest {
base_auth_request: &request.base_request,
destination: &js,
authority: &|js, v| js.set_authority(v),
correlation_id: &|js, v| js.set_correlation_id(v),
}
.iter_all();
if let Some(v) = request.force_refresh {
js.set_force_refresh(v)
}
if let Some(v) = &request.redirect_uri {
js.set_redirect_uri(v)
}
js
}
}
#[derive(Default, Clone)]
pub struct EndSessionRequest<'a> {
account: Option<&'a AccountInfo>,
post_logout_redirect_uri: Option<Cow<'a, str>>,
authority: Option<Cow<'a, str>>,
correlation_id: Option<Cow<'a, str>>,
}
impl<'a> EndSessionRequest<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn set_account(mut self, account: &'a AccountInfo) -> Self {
self.account = Some(account);
self
}
pub fn set_post_logout_redirect_uri<T>(mut self, post_logout_redirect_uri: T) -> Self
where
T: Into<Cow<'a, str>>,
{
self.post_logout_redirect_uri = Some(post_logout_redirect_uri.into());
self
}
pub fn set_authority<T>(mut self, authority: T) -> Self
where
T: Into<Cow<'a, str>>,
{
self.authority = Some(authority.into());
self
}
pub fn set_correlation_id<T>(mut self, correlation_id: T) -> Self
where
T: Into<Cow<'a, str>>,
{
self.correlation_id = Some(correlation_id.into());
self
}
}
impl<'a> From<EndSessionRequest<'a>> for msal::EndSessionRequest {
fn from(request: EndSessionRequest) -> Self {
let js = msal::EndSessionRequest::new();
if let Some(v) = request.account {
js.set_account(v.into())
}
if let Some(v) = &request.post_logout_redirect_uri {
js.set_post_logout_redirect_uri(&v)
}
if let Some(v) = &request.authority {
js.set_authority(&v)
}
if let Some(v) = &request.correlation_id {
js.set_correlation_id(&v)
}
js
}
}
#[cfg(test)]
mod test_request {
wasm_bindgen_test_configure!(run_in_browser);
use super::*;
use crate::tests::*;
use wasm_bindgen_test::*;
use web_sys::console;
const FORCE_REFRESH: bool = true;
const REDIRECT_URI: &str = "redirect_uri";
#[wasm_bindgen_test]
fn mirror_auth_url_request() {
let req = AuthorizationUrlRequest::new(&[SCOPE][..]);
let js_req: msal::AuthorizationUrlRequest = (&req).into();
console::log_1(&"AuthorizationUrlRequest:".into());
console::log_1(&js_req);
js_cast_checker::<msal::AuthorizationUrlRequest>(js_req.into());
}
#[wasm_bindgen_test]
#[cfg(feature = "redirect")]
fn mirror_redirect_request() {
let req = RedirectRequest::new(&[SCOPE][..]);
let js_req: msal::RedirectRequest = (&req).into();
console::log_1(&"RedirectRequest:".into());
console::log_1(&js_req);
js_cast_checker::<msal::RedirectRequest>(js_req.into());
}
#[wasm_bindgen_test]
fn mirror_silent_request() {
let account = account();
let req = SilentRequest::new(&[SCOPE][..], &account)
.set_force_refresh(FORCE_REFRESH)
.set_redirect_uri(REDIRECT_URI)
.set_correlation_id(CORRELATION_ID)
.set_authority(AUTHORITY);
let js_req: msal::SilentRequest = (&req).into();
console::log_1(&"SilentRequest:".into());
console::log_1(&js_req);
assert_eq!(
req.base_request.scopes,
JsArrayString::from(js_req.scopes()).0
);
assert_eq!(
req.base_request.correlation_id.as_deref().map(String::from),
js_req.correlation_id()
);
assert_eq!(
req.base_request.authority.as_deref().map(String::from),
js_req.authority()
);
assert_eq!(
req.account.home_account_id,
js_req.account().home_account_id()
);
assert_eq!(req.account.environment, js_req.account().environment());
assert_eq!(req.account.tenant_id, js_req.account().tenant_id());
assert_eq!(req.account.username, js_req.account().username());
assert_eq!(req.force_refresh, js_req.force_refresh());
assert_eq!(req.redirect_uri.map(Cow::into_owned), js_req.redirect_uri());
js_cast_checker::<msal::SilentRequest>(js_req.into());
}
#[wasm_bindgen_test]
fn mirror_end_session_request() {
let account = account();
let req = EndSessionRequest::default()
.set_account(&account)
.set_authority(AUTHORITY)
.set_correlation_id(CORRELATION_ID)
.set_post_logout_redirect_uri(POST_LOGOUT_URI);
let js_req: msal::EndSessionRequest = req.clone().into();
console::log_1(&"EndSessionRequest:".into());
console::log_1(&js_req);
assert_eq!(
req.correlation_id.map(Cow::into_owned),
js_req.correlation_id()
);
assert_eq!(
req.post_logout_redirect_uri.map(Cow::into_owned),
js_req.post_logout_redirect_uri()
);
assert_eq!(req.authority.map(Cow::into_owned), js_req.authority());
assert_eq!(
req.account.as_ref().unwrap().home_account_id,
js_req.account().unwrap().home_account_id()
);
assert_eq!(
req.account.as_ref().unwrap().environment,
js_req.account().unwrap().environment()
);
assert_eq!(
req.account.as_ref().unwrap().tenant_id,
js_req.account().unwrap().tenant_id()
);
assert_eq!(
req.account.as_ref().unwrap().username,
js_req.account().unwrap().username()
);
js_cast_checker::<msal::EndSessionRequest>(js_req.into());
}
}