use crate::idm::server::IdmServerProxyReadTransaction;
use crate::prelude::*;
use kanidm_proto::internal::AppLink;
impl<'a> IdmServerProxyReadTransaction<'a> {
pub fn list_applinks(&mut self, ident: &Identity) -> Result<Vec<AppLink>, OperationError> {
let Some(ident_mo) = ident.get_memberof() else {
debug!("Ident has no memberof, no applinks are present");
return Ok(Vec::with_capacity(0));
};
let f_executed = filter!(f_or(
ident_mo
.iter()
.copied()
.map(|uuid| { f_eq(Attribute::OAuth2RsScopeMap, PartialValue::Refer(uuid)) })
.collect()
));
let f_intent = filter!(f_eq(
Attribute::Class,
EntryClass::OAuth2ResourceServer.into()
));
let oauth2_related = self
.qs_read
.impersonate_search_ext(f_executed, f_intent, ident)?;
trace!(?oauth2_related);
let apps = oauth2_related
.iter()
.filter_map(|entry| {
let display_name = entry
.get_ava_single_utf8(Attribute::DisplayName)
.map(str::to_string)?;
let redirect_url = entry
.get_ava_single_url(Attribute::OAuth2RsOriginLanding)
.cloned()?;
let name = entry
.get_ava_single_iname(Attribute::Name)
.map(str::to_string)?;
let has_image = entry.get_ava_single_image(Attribute::Image).is_some();
Some(AppLink::Oauth2 {
name,
display_name,
redirect_url,
has_image,
})
})
.collect::<Vec<_>>();
debug!("returned {} related apps", apps.len());
trace!(?apps);
Ok(apps)
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use kanidm_proto::internal::AppLink;
#[idm_test]
async fn test_idm_applinks_list(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
let ct = duration_from_epoch_now();
let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
let usr_uuid = Uuid::new_v4();
let grp_uuid = Uuid::new_v4();
let e_rs: Entry<EntryInit, EntryNew> = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(
Attribute::Class,
EntryClass::OAuth2ResourceServer.to_value()
),
(
Attribute::Class,
EntryClass::OAuth2ResourceServerBasic.to_value()
),
(Attribute::Name, Value::new_iname("test_resource_server")),
(
Attribute::DisplayName,
Value::new_utf8s("test_resource_server")
),
(
Attribute::OAuth2RsOrigin,
Value::new_url_s("https://demo.example.com").unwrap()
),
(
Attribute::OAuth2RsOriginLanding,
Value::new_url_s("https://demo.example.com/landing").unwrap()
),
(
Attribute::OAuth2RsScopeMap,
Value::new_oauthscopemap(
grp_uuid,
btreeset![kanidm_proto::constants::OAUTH2_SCOPE_READ.to_string()]
)
.expect("invalid oauthscope")
)
);
let e_usr = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testaccount")),
(Attribute::Uuid, Value::Uuid(usr_uuid)),
(Attribute::Description, Value::new_utf8s("testaccount")),
(Attribute::DisplayName, Value::new_utf8s("Test Account"))
);
let e_grp = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Uuid, Value::Uuid(grp_uuid)),
(Attribute::Name, Value::new_iname("test_oauth2_group"))
);
let ce = CreateEvent::new_internal(vec![e_rs, e_grp, e_usr]);
assert!(idms_prox_write.qs_write.create(&ce).is_ok());
assert!(idms_prox_write.commit().is_ok());
let mut idms_prox_read = idms.proxy_read().await.unwrap();
let ident = idms_prox_read
.qs_read
.internal_search_uuid(usr_uuid)
.map(Identity::from_impersonate_entry_readonly)
.expect("Failed to impersonate identity");
let apps = idms_prox_read
.list_applinks(&ident)
.expect("Failed to access related apps");
assert!(apps.is_empty());
drop(idms_prox_read);
let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
let me_inv_m = ModifyEvent::new_internal_invalid(
filter!(f_eq(Attribute::Uuid, PartialValue::Refer(grp_uuid))),
ModifyList::new_append(Attribute::Member, Value::Refer(usr_uuid)),
);
assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
assert!(idms_prox_write.commit().is_ok());
let mut idms_prox_read = idms.proxy_read().await.unwrap();
let ident = idms_prox_read
.qs_read
.internal_search_uuid(usr_uuid)
.map(Identity::from_impersonate_entry_readonly)
.expect("Failed to impersonate identity");
let apps = idms_prox_read
.list_applinks(&ident)
.expect("Failed to access related apps");
let app = apps.first().expect("No apps return!");
assert!(match app {
AppLink::Oauth2 {
name,
display_name,
redirect_url,
has_image,
} => {
name == "test_resource_server"
&& display_name == "test_resource_server"
&& redirect_url
== &Url::parse("https://demo.example.com/landing")
.expect("Failed to parse URL")
&& !has_image
} })
}
}