mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
636 consent remembering in oauth2 (#824)
This commit is contained in:
parent
4a1df985b9
commit
9d929b876c
|
@ -57,18 +57,23 @@ pub struct AuthorisationRequestOidc {
|
|||
pub acr: Option<String>,
|
||||
}
|
||||
|
||||
/// We ask our user to consent to this Authorisation Request with the
|
||||
/// following data.
|
||||
/// When we request to authorise, it can either prompt us for consent,
|
||||
/// or it can immediately be granted due the past grant.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ConsentRequest {
|
||||
// A pretty-name of the client
|
||||
pub client_name: String,
|
||||
// A list of scopes requested / to be issued.
|
||||
pub scopes: Vec<String>,
|
||||
// The users displayname (?)
|
||||
// pub display_name: String,
|
||||
// The token we need to be given back to allow this to proceed
|
||||
pub consent_token: String,
|
||||
pub enum AuthorisationResponse {
|
||||
ConsentRequested {
|
||||
// A pretty-name of the client
|
||||
client_name: String,
|
||||
// A list of scopes requested / to be issued.
|
||||
scopes: Vec<String>,
|
||||
// Extra PII that may be requested
|
||||
pii_scopes: Vec<String>,
|
||||
// The users displayname (?)
|
||||
// pub display_name: String,
|
||||
// The token we need to be given back to allow this to proceed
|
||||
consent_token: String,
|
||||
},
|
||||
Permitted,
|
||||
}
|
||||
|
||||
// The resource server then contacts the token endpoint with
|
||||
|
|
|
@ -11,8 +11,8 @@ LABEL maintainer william@blackhats.net.au
|
|||
|
||||
RUN zypper install -y \
|
||||
cargo \
|
||||
rust \
|
||||
gcc clang lld \
|
||||
rust wasm-pack \
|
||||
clang lld \
|
||||
make automake autoconf \
|
||||
libopenssl-devel pam-devel \
|
||||
sqlite3-devel \
|
||||
|
@ -20,7 +20,6 @@ RUN zypper install -y \
|
|||
zypper clean -a
|
||||
|
||||
COPY . /usr/src/kanidm
|
||||
WORKDIR /usr/src/kanidm/kanidmd/daemon
|
||||
|
||||
ARG SCCACHE_REDIS=""
|
||||
ARG KANIDM_FEATURES
|
||||
|
@ -31,25 +30,38 @@ RUN mkdir /scratch
|
|||
RUN echo $KANIDM_BUILD_PROFILE
|
||||
RUN echo $KANIDM_FEATURES
|
||||
|
||||
ENV RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/bin/ld.lld"
|
||||
ENV CARGO_HOME=/scratch/.cargo
|
||||
ENV RUSTFLAGS="-Clinker=clang"
|
||||
|
||||
WORKDIR /usr/src/kanidm/kanidmd_web_ui
|
||||
RUN if [ "${SCCACHE_REDIS}" != "" ]; \
|
||||
then \
|
||||
export CARGO_INCREMENTAL=false && \
|
||||
export RUSTC_WRAPPER=sccache && \
|
||||
sccache --start-server; \
|
||||
fi && \
|
||||
./build_wasm_dev.sh
|
||||
|
||||
WORKDIR /usr/src/kanidm/kanidmd/daemon
|
||||
|
||||
ENV RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/bin/ld.lld"
|
||||
|
||||
RUN if [ "${SCCACHE_REDIS}" != "" ]; \
|
||||
then \
|
||||
export CC="/usr/bin/sccache /usr/bin/clang" && \
|
||||
export CARGO_INCREMENTAL=false && \
|
||||
export RUSTC_WRAPPER=sccache && \
|
||||
sccache --start-server; \
|
||||
else \
|
||||
export CC="/usr/bin/clang"; \
|
||||
fi && \
|
||||
then \
|
||||
export CC="/usr/bin/sccache /usr/bin/clang" && \
|
||||
export CARGO_INCREMENTAL=false && \
|
||||
export RUSTC_WRAPPER=sccache && \
|
||||
sccache --start-server; \
|
||||
else \
|
||||
export CC="/usr/bin/clang"; \
|
||||
fi && \
|
||||
cargo build ${KANIDM_BUILD_OPTIONS} \
|
||||
--features=${KANIDM_FEATURES} \
|
||||
--target-dir=/usr/src/kanidm/target/ \
|
||||
--release && \
|
||||
--features=${KANIDM_FEATURES} \
|
||||
--target-dir=/usr/src/kanidm/target/ \
|
||||
--release && \
|
||||
if [ "${SCCACHE_REDIS}" != "" ]; \
|
||||
then sccache -s; \
|
||||
fi;
|
||||
then sccache -s; \
|
||||
fi;
|
||||
|
||||
RUN ls -al /usr/src/kanidm/target/release
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ path = "src/main.rs"
|
|||
[dependencies]
|
||||
kanidm = { path = "../idm" }
|
||||
score = { path = "../score" }
|
||||
clap = { version = "^3.2", features = ["derive"] }
|
||||
clap = { version = "^3.2", features = ["derive", "env"] }
|
||||
users = "^0.11.0"
|
||||
serde = { version = "^1.0.137", features = ["derive"] }
|
||||
tokio = { version = "^1.19.1", features = ["rt-multi-thread", "macros", "signal"] }
|
||||
|
|
|
@ -20,8 +20,8 @@ use kanidm_proto::v1::{BackupCodesView, OperationError, RadiusAuthToken};
|
|||
use crate::filter::{Filter, FilterInvalid};
|
||||
use crate::idm::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
||||
AccessTokenResponse, AuthorisationRequest, AuthorisePermitSuccess, ConsentRequest, JwkKeySet,
|
||||
Oauth2Error, OidcDiscoveryResponse, OidcToken,
|
||||
AccessTokenResponse, AuthorisationRequest, AuthorisePermitSuccess, AuthoriseResponse,
|
||||
JwkKeySet, Oauth2Error, OidcDiscoveryResponse, OidcToken,
|
||||
};
|
||||
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
||||
use crate::ldap::{LdapBoundToken, LdapResponseState, LdapServer};
|
||||
|
@ -1120,7 +1120,7 @@ impl QueryServerReadV1 {
|
|||
uat: Option<String>,
|
||||
auth_req: AuthorisationRequest,
|
||||
eventid: Uuid,
|
||||
) -> Result<ConsentRequest, Oauth2Error> {
|
||||
) -> Result<AuthoriseResponse, Oauth2Error> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let idms_prox_read = self.idms.proxy_read_async().await;
|
||||
let res = spanned!("actors::v1_read::handle<Oauth2Authorise>", {
|
||||
|
|
|
@ -718,6 +718,37 @@ pub const JSON_SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES: &str = r#"{
|
|||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"system",
|
||||
"attributetype"
|
||||
],
|
||||
"description": [
|
||||
"A set of scopes mapped from a relying server to a user, where the user has previously consented to the following. If changed or deleted, consent will be re-sought."
|
||||
],
|
||||
"index": [
|
||||
"EQUALITY"
|
||||
],
|
||||
"unique": [
|
||||
"false"
|
||||
],
|
||||
"multivalue": [
|
||||
"true"
|
||||
],
|
||||
"attributename": [
|
||||
"oauth2_consent_scope_map"
|
||||
],
|
||||
"syntax": [
|
||||
"OAUTH_SCOPE_MAP"
|
||||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000097"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_SCHEMA_ATTR_ES256_PRIVATE_KEY_DER: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
|
@ -973,7 +1004,8 @@ pub const JSON_SCHEMA_CLASS_ACCOUNT: &str = r#"
|
|||
"radius_secret",
|
||||
"account_expire",
|
||||
"account_valid_from",
|
||||
"mail"
|
||||
"mail",
|
||||
"oauth2_consent_scope_map"
|
||||
],
|
||||
"systemmust": [
|
||||
"displayname",
|
||||
|
|
|
@ -167,6 +167,8 @@ pub const UUID_SCHEMA_ATTR_FERNET_PRIVATE_KEY_STR: Uuid =
|
|||
uuid!("00000000-0000-0000-0000-ffff00000095");
|
||||
pub const _UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffff00000096");
|
||||
pub const _UUID_SCHEMA_CLASS_OAUTH2_CONSENT_SCOPE_MAP: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffff00000097");
|
||||
|
||||
// System and domain infos
|
||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
use crate::prelude::*;
|
||||
use kanidm_proto::v1::UserAuthToken;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -161,4 +162,14 @@ impl Identity {
|
|||
.attribute_equality("memberof", &PartialValue::new_refer(group)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_oauth2_consent_scopes(&self, oauth2_rs: Uuid) -> Option<&BTreeSet<String>> {
|
||||
match &self.origin {
|
||||
IdentType::Internal => None,
|
||||
IdentType::User(u) => u
|
||||
.entry
|
||||
.get_ava_as_oauthscopemaps("oauth2_consent_scope_map")
|
||||
.and_then(|scope_map| scope_map.get(&oauth2_rs)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ pub(crate) enum DelayedAction {
|
|||
UnixPwUpgrade(UnixPasswordUpgrade),
|
||||
WebauthnCounterIncrement(WebauthnCounterIncrement),
|
||||
BackupCodeRemoval(BackupCodeRemoval),
|
||||
Oauth2ConsentGrant(Oauth2ConsentGrant),
|
||||
}
|
||||
|
||||
pub(crate) struct PasswordUpgrade {
|
||||
|
@ -28,3 +29,9 @@ pub(crate) struct BackupCodeRemoval {
|
|||
pub target_uuid: Uuid,
|
||||
pub code_to_remove: String,
|
||||
}
|
||||
|
||||
pub(crate) struct Oauth2ConsentGrant {
|
||||
pub target_uuid: Uuid,
|
||||
pub oauth2_rs_uuid: Uuid,
|
||||
pub scopes: Vec<String>,
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,8 +17,8 @@ use crate::idm::event::{
|
|||
use crate::idm::mfareg::{MfaRegCred, MfaRegNext, MfaRegSession};
|
||||
use crate::idm::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
||||
AccessTokenResponse, AuthorisationRequest, AuthorisePermitSuccess, ConsentRequest, JwkKeySet,
|
||||
Oauth2Error, Oauth2ResourceServers, Oauth2ResourceServersReadTransaction,
|
||||
AccessTokenResponse, AuthorisationRequest, AuthorisePermitSuccess, AuthoriseResponse,
|
||||
JwkKeySet, Oauth2Error, Oauth2ResourceServers, Oauth2ResourceServersReadTransaction,
|
||||
Oauth2ResourceServersWriteTransaction, OidcDiscoveryResponse, OidcToken,
|
||||
};
|
||||
use crate::idm::radius::RadiusAccount;
|
||||
|
@ -34,7 +34,8 @@ use crate::utils::{
|
|||
|
||||
use crate::actors::v1_write::QueryServerWriteV1;
|
||||
use crate::idm::delayed::{
|
||||
DelayedAction, PasswordUpgrade, UnixPasswordUpgrade, WebauthnCounterIncrement,
|
||||
DelayedAction, Oauth2ConsentGrant, PasswordUpgrade, UnixPasswordUpgrade,
|
||||
WebauthnCounterIncrement,
|
||||
};
|
||||
|
||||
use hashbrown::HashSet;
|
||||
|
@ -138,6 +139,7 @@ pub struct IdmServerProxyReadTransaction<'a> {
|
|||
pub qs_read: QueryServerReadTransaction<'a>,
|
||||
uat_jwt_validator: CowCellReadTxn<JwsValidator>,
|
||||
oauth2rs: Oauth2ResourceServersReadTransaction,
|
||||
async_tx: Sender<DelayedAction>,
|
||||
}
|
||||
|
||||
pub struct IdmServerProxyWriteTransaction<'a> {
|
||||
|
@ -158,7 +160,7 @@ pub struct IdmServerProxyWriteTransaction<'a> {
|
|||
}
|
||||
|
||||
pub struct IdmServerDelayed {
|
||||
async_rx: Receiver<DelayedAction>,
|
||||
pub(crate) async_rx: Receiver<DelayedAction>,
|
||||
}
|
||||
|
||||
impl IdmServer {
|
||||
|
@ -307,6 +309,7 @@ impl IdmServer {
|
|||
qs_read: self.qs.read_async().await,
|
||||
uat_jwt_validator: self.uat_jwt_validator.read(),
|
||||
oauth2rs: self.oauth2rs.read(),
|
||||
async_tx: self.async_tx.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1177,7 +1180,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
|||
uat: &UserAuthToken,
|
||||
auth_req: &AuthorisationRequest,
|
||||
ct: Duration,
|
||||
) -> Result<ConsentRequest, Oauth2Error> {
|
||||
) -> Result<AuthoriseResponse, Oauth2Error> {
|
||||
self.oauth2rs
|
||||
.check_oauth2_authorisation(ident, uat, auth_req, ct)
|
||||
}
|
||||
|
@ -1190,7 +1193,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
|||
ct: Duration,
|
||||
) -> Result<AuthorisePermitSuccess, OperationError> {
|
||||
self.oauth2rs
|
||||
.check_oauth2_authorise_permit(ident, uat, consent_req, ct)
|
||||
.check_oauth2_authorise_permit(ident, uat, consent_req, ct, &self.async_tx)
|
||||
}
|
||||
|
||||
pub fn check_oauth2_authorise_reject(
|
||||
|
@ -2044,6 +2047,27 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn process_oauth2consentgrant(
|
||||
&mut self,
|
||||
o2cg: &Oauth2ConsentGrant,
|
||||
) -> Result<(), OperationError> {
|
||||
let modlist = ModifyList::new_list(vec![
|
||||
Modify::Removed(
|
||||
AttrString::from("oauth2_consent_scope_map"),
|
||||
PartialValue::OauthScopeMap(o2cg.oauth2_rs_uuid),
|
||||
),
|
||||
Modify::Present(
|
||||
AttrString::from("oauth2_consent_scope_map"),
|
||||
Value::OauthScopeMap(o2cg.oauth2_rs_uuid, o2cg.scopes.iter().cloned().collect()),
|
||||
),
|
||||
]);
|
||||
|
||||
self.qs_write.internal_modify(
|
||||
&filter_all!(f_eq("uuid", PartialValue::new_uuid(o2cg.target_uuid))),
|
||||
&modlist,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn process_delayedaction(
|
||||
&mut self,
|
||||
da: DelayedAction,
|
||||
|
@ -2053,6 +2077,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
DelayedAction::UnixPwUpgrade(upwu) => self.process_unixpwupgrade(&upwu),
|
||||
DelayedAction::WebauthnCounterIncrement(wci) => self.process_webauthncounterinc(&wci),
|
||||
DelayedAction::BackupCodeRemoval(bcr) => self.process_backupcoderemoval(&bcr),
|
||||
DelayedAction::Oauth2ConsentGrant(o2cg) => self.process_oauth2consentgrant(&o2cg),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2318,6 +2318,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
JSON_SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE,
|
||||
JSON_SCHEMA_ATTR_RS256_PRIVATE_KEY_DER,
|
||||
JSON_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||
JSON_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||
JSON_SCHEMA_CLASS_PERSON,
|
||||
JSON_SCHEMA_CLASS_ORGPERSON,
|
||||
JSON_SCHEMA_CLASS_GROUP,
|
||||
|
|
|
@ -2,9 +2,10 @@ use super::v1::{json_rest_event_get, json_rest_event_post};
|
|||
use super::{to_tide_response, AppState, RequestExtensions};
|
||||
use kanidm::idm::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenRequest, AuthorisationRequest, AuthorisePermitSuccess,
|
||||
ErrorResponse, Oauth2Error,
|
||||
AuthoriseResponse, ErrorResponse, Oauth2Error,
|
||||
};
|
||||
use kanidm::prelude::*;
|
||||
use kanidm_proto::oauth2::AuthorisationResponse;
|
||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -185,7 +186,13 @@ pub async fn oauth2_id_delete(req: tide::Request<AppState>) -> tide::Result {
|
|||
|
||||
pub async fn oauth2_authorise_post(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let auth_req: AuthorisationRequest = req.body_json().await?;
|
||||
oauth2_authorise(req, auth_req).await
|
||||
oauth2_authorise(req, auth_req).await.map(|mut res| {
|
||||
if res.status() == 302 {
|
||||
// in post, we need the redirect not to be issued, so we mask 302 to 200
|
||||
res.set_status(200);
|
||||
}
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn oauth2_authorise_get(req: tide::Request<AppState>) -> tide::Result {
|
||||
|
@ -219,12 +226,43 @@ async fn oauth2_authorise(
|
|||
.await;
|
||||
|
||||
match res {
|
||||
Ok(consent_req) => {
|
||||
Ok(AuthoriseResponse::ConsentRequested {
|
||||
client_name,
|
||||
scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
}) => {
|
||||
// Render a redirect to the consent page for the user to interact with
|
||||
// to authorise this session-id
|
||||
let mut res = tide::Response::new(200);
|
||||
// This is json so later we can expand it with better detail.
|
||||
tide::Body::from_json(&consent_req).map(|b| {
|
||||
tide::Body::from_json(&AuthorisationResponse::ConsentRequested {
|
||||
client_name,
|
||||
scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
})
|
||||
.map(|b| {
|
||||
res.set_body(b);
|
||||
res
|
||||
})
|
||||
}
|
||||
Ok(AuthoriseResponse::Permitted(AuthorisePermitSuccess {
|
||||
mut redirect_uri,
|
||||
state,
|
||||
code,
|
||||
})) => {
|
||||
let mut res = tide::Response::new(302);
|
||||
|
||||
redirect_uri
|
||||
.query_pairs_mut()
|
||||
.clear()
|
||||
.append_pair("state", &state)
|
||||
.append_pair("code", &code);
|
||||
res.insert_header("Location", redirect_uri.as_str());
|
||||
// I think the client server needs this
|
||||
res.insert_header("Access-Control-Allow-Origin", redirect_uri.origin().ascii_serialization());
|
||||
tide::Body::from_json(&AuthorisationResponse::Permitted).map(|b| {
|
||||
res.set_body(b);
|
||||
res
|
||||
})
|
||||
|
@ -265,8 +303,10 @@ pub async fn oauth2_authorise_permit_post(mut req: tide::Request<AppState>) -> t
|
|||
oauth2_authorise_permit(req, consent_req)
|
||||
.await
|
||||
.map(|mut res| {
|
||||
// in post, we need the redirect not to be issued, so we mask 302 to 200
|
||||
res.set_status(200);
|
||||
if res.status() == 302 {
|
||||
// in post, we need the redirect not to be issued, so we mask 302 to 200
|
||||
res.set_status(200);
|
||||
}
|
||||
res
|
||||
})
|
||||
}
|
||||
|
@ -318,7 +358,7 @@ async fn oauth2_authorise_permit(
|
|||
.append_pair("code", &code);
|
||||
res.insert_header("Location", redirect_uri.as_str());
|
||||
// I think the client server needs this
|
||||
// res.insert_header("Access-Control-Allow-Origin", redirect_uri.origin().ascii_serialization());
|
||||
res.insert_header("Access-Control-Allow-Origin", redirect_uri.origin().ascii_serialization());
|
||||
res
|
||||
}
|
||||
Err(_e) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::common::{setup_async_test, ADMIN_TEST_PASSWORD};
|
|||
use compact_jwt::{JwkKeySet, JwsValidator, OidcToken, OidcUnverified};
|
||||
use kanidm_proto::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
||||
AccessTokenResponse, ConsentRequest, OidcDiscoveryResponse,
|
||||
AccessTokenResponse, AuthorisationResponse, OidcDiscoveryResponse,
|
||||
};
|
||||
use oauth2_ext::PkceCodeChallenge;
|
||||
use std::collections::HashMap;
|
||||
|
@ -217,18 +217,25 @@ async fn test_oauth2_openid_basic_flow() {
|
|||
assert!(response.status() == reqwest::StatusCode::OK);
|
||||
assert_no_cache!(response);
|
||||
|
||||
let consent_req: ConsentRequest = response
|
||||
let consent_req: AuthorisationResponse = response
|
||||
.json()
|
||||
.await
|
||||
.expect("Failed to access response body");
|
||||
|
||||
let consent_token =
|
||||
if let AuthorisationResponse::ConsentRequested { consent_token, .. } = consent_req {
|
||||
consent_token
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
// Step 2 - we now send the consent get to the server which yields a redirect with a
|
||||
// state and code.
|
||||
|
||||
let response = client
|
||||
.get(format!("{}/oauth2/authorise/permit", url))
|
||||
.bearer_auth(admin_uat)
|
||||
.query(&[("token", consent_req.consent_token.as_str())])
|
||||
.query(&[("token", consent_token.as_str())])
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request.");
|
||||
|
|
|
@ -24,7 +24,7 @@ If you want to deploy Kanidm to see what it can do, you should read the kanidm b
|
|||
- [Kanidm book (Latest stable)](https://kanidm.github.io/kanidm/stable/)
|
||||
|
||||
|
||||
We also publish limited [support guidelines](https://github.com/kanidm/kanidm/blob/master/project_docs/RELEASE_AND_SUPPORT.md)).
|
||||
We also publish limited [support guidelines](https://github.com/kanidm/kanidm/blob/master/project_docs/RELEASE_AND_SUPPORT.md).
|
||||
|
||||
## Code of Conduct / Ethics
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<text y=".9em" font-size="90">🦀</text>
|
||||
</svg>
|
Before Width: | Height: | Size: 111 B |
6
kanidmd_web_ui/pkg/kanidmd_web_ui.d.ts
vendored
6
kanidmd_web_ui/pkg/kanidmd_web_ui.d.ts
vendored
|
@ -12,9 +12,9 @@ export interface InitOutput {
|
|||
readonly __wbindgen_malloc: (a: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
readonly _dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha6256632cf9b6e15: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h026107843485189d: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf1579e791c670fad: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h517d7fce3d158796: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6c41bce435f08bdb: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hea19f293916f5b3a: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_free: (a: number, b: number) => void;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
|
|
|
@ -241,7 +241,7 @@ function logError(f, args) {
|
|||
function __wbg_adapter_30(arg0, arg1, arg2) {
|
||||
_assertNum(arg0);
|
||||
_assertNum(arg1);
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha6256632cf9b6e15(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h517d7fce3d158796(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
|
@ -271,7 +271,7 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
|||
function __wbg_adapter_33(arg0, arg1, arg2) {
|
||||
_assertNum(arg0);
|
||||
_assertNum(arg1);
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h026107843485189d(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6c41bce435f08bdb(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
let stack_pointer = 32;
|
||||
|
@ -285,7 +285,7 @@ function __wbg_adapter_36(arg0, arg1, arg2) {
|
|||
try {
|
||||
_assertNum(arg0);
|
||||
_assertNum(arg1);
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf1579e791c670fad(arg0, arg1, addBorrowedObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hea19f293916f5b3a(arg0, arg1, addBorrowedObject(arg2));
|
||||
} finally {
|
||||
heap[stack_pointer++] = undefined;
|
||||
}
|
||||
|
@ -425,6 +425,9 @@ async function init(input) {
|
|||
const ret = arg0;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_error_09919627ac0992f5 = function() { return logError(function (arg0, arg1) {
|
||||
try {
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
|
@ -443,9 +446,6 @@ async function init(input) {
|
|||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function(arg0) {
|
||||
const obj = takeObject(arg0).original;
|
||||
if (obj.cnt-- == 1) {
|
||||
|
@ -869,16 +869,16 @@ async function init(input) {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper19774 = function() { return logError(function (arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1253, __wbg_adapter_30);
|
||||
imports.wbg.__wbindgen_closure_wrapper19806 = function() { return logError(function (arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1261, __wbg_adapter_30);
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_closure_wrapper21955 = function() { return logError(function (arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1273, __wbg_adapter_33);
|
||||
imports.wbg.__wbindgen_closure_wrapper21982 = function() { return logError(function (arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1281, __wbg_adapter_33);
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_closure_wrapper22397 = function() { return logError(function (arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1300, __wbg_adapter_36);
|
||||
imports.wbg.__wbindgen_closure_wrapper22336 = function() { return logError(function (arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1307, __wbg_adapter_36);
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
|
||||
|
|
Binary file not shown.
|
@ -5,9 +5,9 @@ export function run_app(a: number): void;
|
|||
export function __wbindgen_malloc(a: number): number;
|
||||
export function __wbindgen_realloc(a: number, b: number, c: number): number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
export function _dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha6256632cf9b6e15(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h026107843485189d(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf1579e791c670fad(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h517d7fce3d158796(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6c41bce435f08bdb(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hea19f293916f5b3a(a: number, b: number, c: number): void;
|
||||
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||
export function __wbindgen_free(a: number, b: number): void;
|
||||
export function __wbindgen_exn_store(a: number): void;
|
||||
|
|
|
@ -14,8 +14,8 @@ use crate::models;
|
|||
use crate::utils;
|
||||
|
||||
pub use kanidm_proto::oauth2::{
|
||||
AccessTokenRequest, AccessTokenResponse, AuthorisationRequest, CodeChallengeMethod,
|
||||
ConsentRequest, ErrorResponse,
|
||||
AccessTokenRequest, AccessTokenResponse, AuthorisationRequest, AuthorisationResponse,
|
||||
CodeChallengeMethod, ErrorResponse,
|
||||
};
|
||||
|
||||
enum State {
|
||||
|
@ -25,7 +25,13 @@ enum State {
|
|||
TokenCheck(String),
|
||||
// Token check done, lets do it.
|
||||
SubmitAuthReq(String),
|
||||
Consent(String, ConsentRequest),
|
||||
Consent {
|
||||
token: String,
|
||||
client_name: String,
|
||||
scopes: Vec<String>,
|
||||
pii_scopes: Vec<String>,
|
||||
consent_token: String,
|
||||
},
|
||||
ConsentGranted,
|
||||
ErrInvalidRequest,
|
||||
}
|
||||
|
@ -38,9 +44,17 @@ pub enum Oauth2Msg {
|
|||
LoginProceed,
|
||||
ConsentGranted,
|
||||
TokenValid,
|
||||
Consent(ConsentRequest),
|
||||
Consent {
|
||||
client_name: String,
|
||||
scopes: Vec<String>,
|
||||
pii_scopes: Vec<String>,
|
||||
consent_token: String,
|
||||
},
|
||||
Redirect(String),
|
||||
Error { emsg: String, kopid: Option<String> },
|
||||
Error {
|
||||
emsg: String,
|
||||
kopid: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<FetchError> for Oauth2Msg {
|
||||
|
@ -116,13 +130,41 @@ impl Oauth2App {
|
|||
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
|
||||
let status = resp.status();
|
||||
let headers = resp.headers();
|
||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||
|
||||
if status == 200 {
|
||||
let jsval = JsFuture::from(resp.json()?).await?;
|
||||
let state: ConsentRequest = jsval.into_serde().expect_throw("Invalid response type");
|
||||
Ok(Oauth2Msg::Consent(state))
|
||||
let state: AuthorisationResponse = jsval
|
||||
.into_serde()
|
||||
.map_err(|e| {
|
||||
let e_msg = format!("serde error -> {:?}", e);
|
||||
console::log!(e_msg.as_str());
|
||||
})
|
||||
.expect_throw("Invalid response type");
|
||||
match state {
|
||||
AuthorisationResponse::ConsentRequested {
|
||||
client_name,
|
||||
scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
} => Ok(Oauth2Msg::Consent {
|
||||
client_name,
|
||||
scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
}),
|
||||
AuthorisationResponse::Permitted => {
|
||||
if let Some(loc) = headers.get("location").ok().flatten() {
|
||||
Ok(Oauth2Msg::Redirect(loc))
|
||||
} else {
|
||||
Ok(Oauth2Msg::Error {
|
||||
emsg: "no location header".to_string(),
|
||||
kopid,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||
let text = JsFuture::from(resp.text()?).await?;
|
||||
let emsg = text.as_string().unwrap_or_else(|| "".to_string());
|
||||
Ok(Oauth2Msg::Error { emsg, kopid })
|
||||
|
@ -131,9 +173,9 @@ impl Oauth2App {
|
|||
|
||||
async fn fetch_consent_token(
|
||||
token: String,
|
||||
consent_req: ConsentRequest,
|
||||
consent_token: String,
|
||||
) -> Result<Oauth2Msg, FetchError> {
|
||||
let consentreq_jsvalue = serde_json::to_string(&consent_req.consent_token)
|
||||
let consentreq_jsvalue = serde_json::to_string(&consent_token)
|
||||
.map(|s| JsValue::from(&s))
|
||||
.expect_throw("Failed to serialise consent_req");
|
||||
|
||||
|
@ -294,9 +336,20 @@ impl Component for Oauth2App {
|
|||
};
|
||||
true
|
||||
}
|
||||
Oauth2Msg::Consent(consent_req) => {
|
||||
Oauth2Msg::Consent {
|
||||
client_name,
|
||||
scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
} => {
|
||||
self.state = match &self.state {
|
||||
State::SubmitAuthReq(token) => State::Consent(token.clone(), consent_req),
|
||||
State::SubmitAuthReq(token) => State::Consent {
|
||||
token: token.clone(),
|
||||
client_name,
|
||||
scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
},
|
||||
_ => {
|
||||
console::log!("Invalid state transition");
|
||||
State::ErrInvalidRequest
|
||||
|
@ -306,9 +359,13 @@ impl Component for Oauth2App {
|
|||
}
|
||||
Oauth2Msg::ConsentGranted => {
|
||||
self.state = match &self.state {
|
||||
State::Consent(token, consent_req) => {
|
||||
State::Consent {
|
||||
token,
|
||||
consent_token,
|
||||
..
|
||||
} => {
|
||||
let token_c = token.clone();
|
||||
let cr_c = (*consent_req).clone();
|
||||
let cr_c = consent_token.clone();
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_consent_token(token_c, cr_c).await {
|
||||
Ok(v) => v,
|
||||
|
@ -380,8 +437,36 @@ impl Component for Oauth2App {
|
|||
</main>
|
||||
}
|
||||
}
|
||||
State::Consent(_, query) => {
|
||||
let client_name = query.client_name.clone();
|
||||
State::Consent {
|
||||
token: _,
|
||||
client_name,
|
||||
scopes: _,
|
||||
pii_scopes,
|
||||
consent_token: _,
|
||||
} => {
|
||||
let client_name = client_name.clone();
|
||||
|
||||
let pii_req = if pii_scopes.is_empty() {
|
||||
html! {
|
||||
<div>
|
||||
<p>{ "This site will not have access to your personal information" }</p>
|
||||
<p>{ "If this site requests personal information in the future we will check with you" }</p>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! {
|
||||
<div>
|
||||
<p>{ "This site has requested to see the following personal information" }</p>
|
||||
<ul>
|
||||
{
|
||||
pii_scopes.iter().map(|s| html! { <li>{ s }</li> } ).collect::<Html>()
|
||||
}
|
||||
</ul>
|
||||
<p>{ "If this site requests different personal information in the future we will check with you again" }</p>
|
||||
</div>
|
||||
}
|
||||
};
|
||||
|
||||
// <body class="html-body form-body">
|
||||
html! {
|
||||
<main class="form-signin">
|
||||
|
@ -393,7 +478,9 @@ impl Component for Oauth2App {
|
|||
} ) }
|
||||
action="javascript:void(0);"
|
||||
>
|
||||
<h1 class="h3 mb-3 fw-normal">{"Consent to Proceed to " }{ client_name }</h1>
|
||||
<h2 class="h3 mb-3 fw-normal">{"Consent to Proceed to " }{ client_name }</h2>
|
||||
{ pii_req }
|
||||
|
||||
<button id="autofocus" class="w-100 btn btn-lg btn-primary" type="submit">{ "Proceed" }</button>
|
||||
</form>
|
||||
</main>
|
||||
|
|
Loading…
Reference in a new issue