mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
20221116 oauth2 app portal (#1200)
This commit is contained in:
parent
7c07688fa6
commit
40dd911d10
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -2505,6 +2505,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde-wasm-bindgen 0.4.5",
|
||||
"serde_json",
|
||||
"url",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
@ -5041,9 +5042,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "webauthn-authenticator-rs"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc33594bd26aabc90ce6d081d98617f5d16803660187f88912dead3ac2a9784"
|
||||
checksum = "603b8602cae2d6c3706b6195765ff582389494d10c442d84a1de2ed5a25679ef"
|
||||
dependencies = [
|
||||
"authenticator-ctap2-2021",
|
||||
"base64urlsafedata",
|
||||
|
@ -5075,9 +5076,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "webauthn-rs-core"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ef9a989b5cd2c39c52850c4bc36ebc88907a06ceacf7f80e45d78a308944b9d"
|
||||
checksum = "294c78c83f12153a51e1cf1e6970b5da1397645dada39033a9c3173a8fc4fc2b"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"base64urlsafedata",
|
||||
|
@ -5099,9 +5100,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "webauthn-rs-proto"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585c7662de492733e6ea11e2726270bf34f5d7f8dbd08cd089830fbb3639a229"
|
||||
checksum = "d24e638361a63ba5c0a0be6a60229490fcdf33740ed63df5bb6bdb627b52a138"
|
||||
dependencies = [
|
||||
"base64urlsafedata",
|
||||
"js-sys",
|
||||
|
|
16
kanidm_proto/src/internal.rs
Normal file
16
kanidm_proto/src/internal.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
/// This is a description of a linked or connected application for a user. This is
|
||||
/// used in the UI to render applications on the dashboard for a user to access.
|
||||
pub enum AppLink {
|
||||
Oauth2 {
|
||||
display_name: String,
|
||||
redirect_url: Url,
|
||||
// Future problem.
|
||||
// icon: Icon,
|
||||
|
||||
}
|
||||
}
|
|
@ -14,5 +14,6 @@ pub mod oauth2;
|
|||
pub mod scim_v1;
|
||||
pub mod utils;
|
||||
pub mod v1;
|
||||
pub mod internal;
|
||||
|
||||
pub use webauthn_rs_proto as webauthn;
|
||||
|
|
|
@ -170,15 +170,23 @@ impl LdapServer {
|
|||
// Map the Some(a,v) to ...?
|
||||
|
||||
let ext_filter = match (&sr.scope, req_dn) {
|
||||
(LdapSearchScope::OneLevel, Some(_r)) => return Ok(vec![sr.gen_success()]),
|
||||
(LdapSearchScope::OneLevel, None) => {
|
||||
// OneLevel and Child searches are veerrrryyy similar for us because child
|
||||
// is a "subtree search excluding base". Because we don't have a tree structure at
|
||||
// all, this is the same as a onelevel (ald children of base excludeing base).
|
||||
(LdapSearchScope::Children, Some(_r)) | (LdapSearchScope::OneLevel, Some(_r)) => {
|
||||
return Ok(vec![sr.gen_success()])
|
||||
}
|
||||
(LdapSearchScope::Children, None) | (LdapSearchScope::OneLevel, None) => {
|
||||
// exclude domain_info
|
||||
Some(LdapFilter::Not(Box::new(LdapFilter::Equality(
|
||||
"uuid".to_string(),
|
||||
STR_UUID_DOMAIN_INFO.to_string(),
|
||||
))))
|
||||
}
|
||||
(LdapSearchScope::Base, Some((a, v))) => Some(LdapFilter::Equality(a, v)),
|
||||
// because we request a specific DN, these are the same since we want the same
|
||||
// entry.
|
||||
(LdapSearchScope::Base, Some((a, v)))
|
||||
| (LdapSearchScope::Subtree, Some((a, v))) => Some(LdapFilter::Equality(a, v)),
|
||||
(LdapSearchScope::Base, None) => {
|
||||
// domain_info
|
||||
Some(LdapFilter::Equality(
|
||||
|
@ -186,7 +194,6 @@ impl LdapServer {
|
|||
STR_UUID_DOMAIN_INFO.to_string(),
|
||||
))
|
||||
}
|
||||
(LdapSearchScope::Subtree, Some((a, v))) => Some(LdapFilter::Equality(a, v)),
|
||||
(LdapSearchScope::Subtree, None) => {
|
||||
// No filter changes needed.
|
||||
None
|
||||
|
|
|
@ -35,6 +35,7 @@ uuid = "^1.2.1"
|
|||
wasm-bindgen = { version = "^0.2.81" }
|
||||
wasm-bindgen-futures = { version = "^0.4.30" }
|
||||
wasm-bindgen-test = "0.3.33"
|
||||
url = "^2.3.1"
|
||||
yew = "^0.19.3"
|
||||
yew-agent = "^0.1.0"
|
||||
yew-router = "^0.16.0"
|
||||
|
|
|
@ -1040,16 +1040,16 @@ function getImports() {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper4757 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1013, __wbg_adapter_48);
|
||||
imports.wbg.__wbindgen_closure_wrapper4782 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1015, __wbg_adapter_48);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper4941 = function(arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1037, __wbg_adapter_51);
|
||||
imports.wbg.__wbindgen_closure_wrapper4966 = function(arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1039, __wbg_adapter_51);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5597 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1287, __wbg_adapter_54);
|
||||
imports.wbg.__wbindgen_closure_wrapper5621 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1288, __wbg_adapter_54);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
|
@ -4,7 +4,7 @@ use gloo::console;
|
|||
use yew::{html, Component, Context, Html, Properties};
|
||||
use yew_router::prelude::Link;
|
||||
|
||||
use crate::components::adminmenu::{Entity, EntityType, GetError};
|
||||
use crate::components::admin_menu::{Entity, EntityType, GetError};
|
||||
use crate::components::alpha_warning_banner;
|
||||
use crate::constants::{
|
||||
CSS_BREADCRUMB_ITEM, CSS_BREADCRUMB_ITEM_ACTIVE, CSS_CELL, CSS_DT, CSS_TABLE,
|
||||
|
|
|
@ -4,7 +4,7 @@ use gloo::console;
|
|||
use yew::{html, Component, Context, Html, Properties};
|
||||
use yew_router::prelude::Link;
|
||||
|
||||
use crate::components::adminmenu::{Entity, EntityType, GetError};
|
||||
use crate::components::admin_menu::{Entity, EntityType, GetError};
|
||||
use crate::components::alpha_warning_banner;
|
||||
use crate::constants::{CSS_BREADCRUMB_ITEM, CSS_BREADCRUMB_ITEM_ACTIVE, CSS_CELL, CSS_TABLE};
|
||||
use crate::utils::{do_alert_error, do_page_header, init_request};
|
||||
|
|
|
@ -3,13 +3,10 @@ use yew::{html, Component, Context, Html, Properties};
|
|||
use yew_router::prelude::Link;
|
||||
|
||||
use crate::components::alpha_warning_banner;
|
||||
use crate::constants::{CSS_LINK_DARK_STRETCHED, CSS_PAGE_HEADER};
|
||||
use crate::constants::{CSS_CARD, CSS_CARD_BODY, CSS_LINK_DARK_STRETCHED, CSS_PAGE_HEADER};
|
||||
// use crate::error::FetchError;
|
||||
use crate::views::AdminRoute;
|
||||
|
||||
const CSS_CARD: &str = "card text-center";
|
||||
const CSS_CARD_BODY: &str = "card-body text-center";
|
||||
|
||||
#[derive(Eq, PartialEq, Properties)]
|
||||
pub struct Props;
|
||||
|
|
@ -4,7 +4,7 @@ use gloo::console;
|
|||
use yew::{html, Component, Context, Html, Properties};
|
||||
use yew_router::prelude::Link;
|
||||
|
||||
use crate::components::adminmenu::{Entity, EntityType, GetError};
|
||||
use crate::components::admin_menu::{Entity, EntityType, GetError};
|
||||
use crate::components::alpha_warning_banner;
|
||||
use crate::constants::{CSS_BREADCRUMB_ITEM, CSS_BREADCRUMB_ITEM_ACTIVE, CSS_CELL, CSS_TABLE};
|
||||
use crate::utils::{do_alert_error, do_page_header, init_request};
|
||||
|
|
|
@ -3,8 +3,8 @@ use yew::Html;
|
|||
|
||||
pub mod admin_accounts;
|
||||
pub mod admin_groups;
|
||||
pub mod admin_menu;
|
||||
pub mod admin_oauth2;
|
||||
pub mod adminmenu;
|
||||
pub mod change_unix_password;
|
||||
|
||||
/// creates the "Kanidm is alpha" banner
|
||||
|
|
|
@ -30,3 +30,7 @@ pub const CSS_DT: &str = "col-6";
|
|||
|
||||
pub const CSS_BREADCRUMB_ITEM: &str = "breadcrumb-item";
|
||||
pub const CSS_BREADCRUMB_ITEM_ACTIVE: &str = "breadcrumb-item active";
|
||||
|
||||
// used in the UI for ... cards
|
||||
pub const CSS_CARD: &str = "card text-center";
|
||||
pub const CSS_CARD_BODY: &str = "card-body text-center";
|
||||
|
|
|
@ -244,9 +244,7 @@ impl Component for Oauth2App {
|
|||
};
|
||||
}
|
||||
};
|
||||
|
||||
let e_msg = format!("{:?}", query);
|
||||
console::error!(e_msg.as_str());
|
||||
console::debug!(format!("{query:?}", ));
|
||||
|
||||
// In the query, if this is openid there MAY be a hint
|
||||
// as to the users name.
|
||||
|
|
|
@ -3,22 +3,58 @@ use gloo::console;
|
|||
use yew::prelude::*;
|
||||
|
||||
use crate::components::alpha_warning_banner;
|
||||
use crate::constants::{CSS_CELL, CSS_PAGE_HEADER, CSS_TABLE};
|
||||
use crate::constants::{CSS_CARD, CSS_LINK_DARK_STRETCHED, CSS_PAGE_HEADER, CSS_CARD_BODY};
|
||||
use crate::error::FetchError;
|
||||
use wasm_bindgen::prelude::*;
|
||||
// use crate::utils;
|
||||
// use wasm_bindgen::JsCast;
|
||||
// use wasm_bindgen_futures::JsFuture;
|
||||
// use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
|
||||
|
||||
use kanidm_proto::internal::AppLink;
|
||||
|
||||
pub enum Msg {
|
||||
// Nothing
|
||||
Ready { apps: Vec<AppLink> },
|
||||
Error { emsg: String, kopid: Option<String> },
|
||||
}
|
||||
|
||||
pub struct AppsApp {}
|
||||
impl From<FetchError> for Msg {
|
||||
fn from(fe: FetchError) -> Self {
|
||||
Msg::Error {
|
||||
emsg: fe.as_string(),
|
||||
kopid: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum State {
|
||||
Waiting,
|
||||
Ready { apps: Vec<AppLink> },
|
||||
Error { emsg: String, kopid: Option<String> },
|
||||
}
|
||||
|
||||
pub struct AppsApp {
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl Component for AppsApp {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
#[cfg(debug)]
|
||||
console::debug!("views::apps::create");
|
||||
AppsApp {}
|
||||
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_user_apps().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
|
||||
let state = State::Waiting;
|
||||
|
||||
AppsApp { state }
|
||||
}
|
||||
|
||||
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
||||
|
@ -27,15 +63,14 @@ impl Component for AppsApp {
|
|||
false
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
#[cfg(debug)]
|
||||
console::debug!("views::apps::update");
|
||||
/*
|
||||
match msg {
|
||||
ViewsMsg::Logout => {
|
||||
}
|
||||
Msg::Ready { apps } => self.state = State::Ready { apps },
|
||||
Msg::Error { emsg, kopid } => self.state = State::Error { emsg, kopid },
|
||||
}
|
||||
*/
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -44,44 +79,138 @@ impl Component for AppsApp {
|
|||
console::debug!("views::apps::rendered");
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
match &self.state {
|
||||
State::Waiting => self.view_waiting(),
|
||||
State::Ready { apps } => self.view_ready(ctx, apps.as_slice()),
|
||||
State::Error { emsg, kopid } => self.view_error(ctx, &emsg, kopid.as_deref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppsApp {
|
||||
fn view_waiting(&self) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<div class={CSS_PAGE_HEADER}>
|
||||
<h2>{ "Apps" }</h2>
|
||||
</div>
|
||||
|
||||
{ alpha_warning_banner() }
|
||||
<div class="table-responsive">
|
||||
<table class={CSS_TABLE}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{ "#" }</th>
|
||||
<th scope="col">{ "Header" }</th>
|
||||
<th scope="col">{ "Header" }</th>
|
||||
<th scope="col">{ "Header" }</th>
|
||||
<th scope="col">{ "Header" }</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class={CSS_CELL}>{ "1,001" }</td>
|
||||
<td class={CSS_CELL}>{ "random" }</td>
|
||||
<td class={CSS_CELL}>{ "data" }</td>
|
||||
<td class={CSS_CELL}>{ "placeholder" }</td>
|
||||
<td class={CSS_CELL}>{ "text" }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class={CSS_CELL}>{ "1,015" }</td>
|
||||
<td class={CSS_CELL}>{ "random" }</td>
|
||||
<td class={CSS_CELL}>{ "tabular" }</td>
|
||||
<td class={CSS_CELL}>{ "informaasdftion" }</td>
|
||||
<td class={CSS_CELL}>{ "text" }</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="vert-center">
|
||||
<div class="spinner-border text-dark" role="status">
|
||||
<span class="visually-hidden">{ "Loading..." }</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_ready(&self, _ctx: &Context<Self>, apps: &[AppLink]) -> Html {
|
||||
// Please help me, I don't know how to make a grid look nice 🥺
|
||||
html! {
|
||||
<>
|
||||
<div class={CSS_PAGE_HEADER}>
|
||||
<h2>{ "Applications list" }</h2>
|
||||
</div>
|
||||
{ alpha_warning_banner() }
|
||||
if !apps.is_empty() {
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
|
||||
{
|
||||
apps.iter().map(|applink| {
|
||||
match &applink {
|
||||
AppLink::Oauth2 {
|
||||
display_name, redirect_url
|
||||
} => {
|
||||
let redirect_url = redirect_url.to_string();
|
||||
html!{
|
||||
<div class="col">
|
||||
<div class={CSS_CARD}>
|
||||
<h3>
|
||||
<a href={ redirect_url.clone() } class={CSS_LINK_DARK_STRETCHED}>{ display_name }</a>
|
||||
</h3>
|
||||
<div class={CSS_CARD_BODY}>{ "We could put some text here but that's only if there was an app description!"}
|
||||
</div>
|
||||
// <a href={ redirect_url.clone() }><button href={ redirect_url } class="btn btn-secondary" aria-label="Go to App">
|
||||
// {"Go to App"}
|
||||
// </button></a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}).collect::<Html>()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_error(&self, _ctx: &Context<Self>, msg: &str, kopid: Option<&str>) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<p class="text-center">
|
||||
<img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
|
||||
</p>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h2>{ "An Error Occured 🥺" }</h2>
|
||||
<p>{ msg.to_string() }</p>
|
||||
<p>
|
||||
{
|
||||
if let Some(opid) = kopid.as_ref() {
|
||||
format!("Operation ID: {}", opid)
|
||||
} else {
|
||||
"Local Error".to_string()
|
||||
}
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-center">
|
||||
<a href="/"><button href="/" class="btn btn-secondary" aria-label="Return home">{"Return to the home page"}</button></a>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_user_apps() -> Result<Msg, FetchError> {
|
||||
// WILLIAM TODO - Add an api end point to get these applinks from
|
||||
// kanidm based on what you can access.
|
||||
|
||||
/*
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
let request = Request::new_with_str_and_init("/no/such/route", &opts)?;
|
||||
|
||||
request
|
||||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
|
||||
let status = resp.status();
|
||||
|
||||
if status == 200 {
|
||||
let jsval = JsFuture::from(resp.json()?).await?;
|
||||
let apps: Vec<()> = serde_wasm_bindgen::from_value(jsval)
|
||||
.expect_throw("Invalid response type - auth_init::AuthResponse");
|
||||
Ok(Msg::Ready { apps })
|
||||
} else {
|
||||
let headers = resp.headers();
|
||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||
let text = JsFuture::from(resp.text()?).await?;
|
||||
let emsg = text.as_string().unwrap_or_default();
|
||||
Ok(Msg::Error { emsg, kopid })
|
||||
}
|
||||
*/
|
||||
|
||||
Ok(Msg::Ready {
|
||||
apps: vec![AppLink::Oauth2 {
|
||||
display_name: "Test Application".to_string(),
|
||||
redirect_url: url::Url::parse("http://localhost:8080")
|
||||
.expect_throw("Failed to setup url"),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use gloo::console;
|
||||
use kanidm_proto::v1::{UserAuthToken, UiHint};
|
||||
use kanidm_proto::v1::{UiHint, UserAuthToken};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
@ -7,7 +7,7 @@ use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
|
|||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
use crate::components::{admin_accounts, admin_groups, admin_oauth2, adminmenu};
|
||||
use crate::components::{admin_accounts, admin_groups, admin_menu, admin_oauth2};
|
||||
use crate::error::*;
|
||||
use crate::manager::Route;
|
||||
use crate::{models, utils};
|
||||
|
@ -444,7 +444,7 @@ impl ViewsApp {
|
|||
fn admin_routes(route: &AdminRoute) -> Html {
|
||||
match route {
|
||||
AdminRoute::AdminMenu => html! {
|
||||
<adminmenu::AdminMenu />
|
||||
<admin_menu::AdminMenu />
|
||||
},
|
||||
AdminRoute::AdminListAccounts => html!(
|
||||
<admin_accounts::AdminListAccounts />
|
||||
|
|
Loading…
Reference in a new issue