636 consent remembering in oauth2 (#824)

This commit is contained in:
Firstyear 2022-06-20 11:37:39 +10:00 committed by GitHub
parent 4a1df985b9
commit 9d929b876c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1157 additions and 555 deletions

View file

@ -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

View file

@ -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

View file

@ -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"] }

View file

@ -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>", {

View file

@ -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",

View file

@ -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.

View file

@ -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)),
}
}
}

View file

@ -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

View file

@ -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),
}
}

View file

@ -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,

View file

@ -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) => {

View file

@ -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.");

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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) };

View file

@ -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;

View file

@ -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>