mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
1812 1813 post axum cleanup (#1817)
This commit is contained in:
parent
9f886b85dd
commit
d1f51f0a84
78
server/core/src/https/extractors/mod.rs
Normal file
78
server/core/src/https/extractors/mod.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use axum::{
|
||||
async_trait,
|
||||
extract::{ConnectInfo, FromRequestParts},
|
||||
http::{header::HeaderName, request::Parts, StatusCode},
|
||||
RequestPartsExt,
|
||||
};
|
||||
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use crate::https::ServerState;
|
||||
|
||||
#[allow(clippy::declare_interior_mutable_const)]
|
||||
const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
|
||||
|
||||
pub struct TrustedClientIp(pub IpAddr);
|
||||
|
||||
#[async_trait]
|
||||
impl FromRequestParts<ServerState> for TrustedClientIp {
|
||||
type Rejection = (StatusCode, &'static str);
|
||||
|
||||
#[instrument(level = "debug", skip(state))]
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
state: &ServerState,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
if state.trust_x_forward_for {
|
||||
if let Some(x_forward_for) = parts.headers.get(X_FORWARDED_FOR) {
|
||||
// X forward for may be comma separate.
|
||||
let first = x_forward_for
|
||||
.to_str()
|
||||
.map(|s|
|
||||
// Split on an optional comma, return the first result.
|
||||
s.split(',').next().unwrap_or(s))
|
||||
.map_err(|_| {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"X-Forwarded-For contains invalid data",
|
||||
)
|
||||
})?;
|
||||
|
||||
first.parse::<IpAddr>().map(TrustedClientIp).map_err(|_| {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"X-Forwarded-For contains invalid ip addr",
|
||||
)
|
||||
})
|
||||
} else {
|
||||
let ConnectInfo(addr) =
|
||||
parts
|
||||
.extract::<ConnectInfo<SocketAddr>>()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
error!("Connect info contains invalid IP address");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"connect info contains invalid IP address",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(TrustedClientIp(addr.ip()))
|
||||
}
|
||||
} else {
|
||||
let ConnectInfo(addr) =
|
||||
parts
|
||||
.extract::<ConnectInfo<SocketAddr>>()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
error!("Connect info contains invalid IP address");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"connect info contains invalid IP address",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(TrustedClientIp(addr.ip()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ use axum::{
|
|||
http::{self, Request},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
Extension, TypedHeader,
|
||||
TypedHeader,
|
||||
};
|
||||
use axum_sessions::SessionHandle;
|
||||
use http::HeaderValue;
|
||||
|
@ -21,7 +21,6 @@ pub async fn version_middleware<B>(request: Request<B>, next: Next<B>) -> Respon
|
|||
let mut response = next.run(request).await;
|
||||
let headers = response.headers_mut();
|
||||
headers.insert("X-KANIDM-VERSION", HeaderValue::from_static(KANIDM_VERSION));
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
|
@ -32,16 +31,9 @@ pub struct KOpId {
|
|||
pub uat: Option<String>,
|
||||
}
|
||||
|
||||
impl KOpId {
|
||||
/// Return the event ID as a string
|
||||
pub fn eventid_value(&self) -> String {
|
||||
let res = self.eventid;
|
||||
res.as_hyphenated().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// This runs at the start of the request, adding an extension with `KOpId` which has useful things inside it.
|
||||
pub async fn kopid_start<B>(
|
||||
#[instrument(name = "request", skip_all)]
|
||||
pub async fn kopid_middleware<B>(
|
||||
auth: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
mut request: Request<B>,
|
||||
next: Next<B>,
|
||||
|
@ -66,23 +58,12 @@ pub async fn kopid_start<B>(
|
|||
|
||||
// insert the extension so we can pull it out later
|
||||
request.extensions_mut().insert(KOpId { eventid, uat });
|
||||
next.run(request).await
|
||||
}
|
||||
|
||||
/// This runs at the start of the request, adding an extension with the OperationID
|
||||
pub async fn kopid_end<B>(
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
request: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Response {
|
||||
// generate the event ID
|
||||
// insert the extension so we can pull it out later
|
||||
let mut response = next.run(request).await;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
response.headers_mut().insert(
|
||||
"X-KANIDM-OPID",
|
||||
HeaderValue::from_str(&kopid.eventid_value()).unwrap(),
|
||||
HeaderValue::from_str(&eventid.as_hyphenated().to_string()).unwrap(),
|
||||
);
|
||||
|
||||
response
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod extractors;
|
||||
mod generic;
|
||||
mod javascript;
|
||||
mod manifest;
|
||||
|
@ -60,7 +61,7 @@ pub struct ServerState {
|
|||
pub jws_validator: compact_jwt::JwsValidator,
|
||||
// The SHA384 hashes of javascript files we're going to serve to users
|
||||
pub js_files: Vec<JavaScriptFile>,
|
||||
// pub(crate) trust_x_forward_for: bool,
|
||||
pub(crate) trust_x_forward_for: bool,
|
||||
pub csp_header: HeaderValue,
|
||||
}
|
||||
|
||||
|
@ -133,8 +134,7 @@ pub fn get_js_files(role: ServerRole) -> Vec<JavaScriptFile> {
|
|||
|
||||
pub async fn create_https_server(
|
||||
config: Configuration,
|
||||
// trust_x_forward_for: bool, // TODO: #1787 make XFF headers work
|
||||
cookie_key: [u8; 32],
|
||||
cookie_key: [u8; 64],
|
||||
jws_signer: JwsSigner,
|
||||
status_ref: &'static StatusActor,
|
||||
qe_w_ref: &'static QueryServerWriteV1,
|
||||
|
@ -186,15 +186,16 @@ pub async fn create_https_server(
|
|||
);
|
||||
|
||||
let store = async_session::CookieStore::new();
|
||||
let secret = format!("{:?}", cookie_key);
|
||||
let secret = secret.as_bytes(); // TODO the cookie/session secret needs to be longer?
|
||||
let session_layer = SessionLayer::new(store, secret)
|
||||
|
||||
let session_layer = SessionLayer::new(store, &cookie_key)
|
||||
.with_cookie_name("kanidm-session")
|
||||
.with_session_ttl(None)
|
||||
.with_cookie_domain(config.domain)
|
||||
.with_same_site_policy(SameSite::Strict)
|
||||
.with_secure(true);
|
||||
|
||||
let trust_x_forward_for = config.trust_x_forward_for;
|
||||
|
||||
let state = ServerState {
|
||||
status_ref,
|
||||
qe_w_ref,
|
||||
|
@ -202,6 +203,7 @@ pub async fn create_https_server(
|
|||
jws_signer,
|
||||
jws_validator,
|
||||
js_files,
|
||||
trust_x_forward_for,
|
||||
csp_header: csp_header.finish(),
|
||||
};
|
||||
|
||||
|
@ -219,15 +221,13 @@ pub async fn create_https_server(
|
|||
ServerRole::WriteReplicaNoUI => Router::new(),
|
||||
};
|
||||
|
||||
// // == oauth endpoints.
|
||||
// TODO: turn this from a nest into a merge because state things are bad in nested routes
|
||||
let app = Router::new()
|
||||
.nest("/oauth2", oauth2::oauth2_route_setup(state.clone()))
|
||||
.nest("/scim", v1_scim::scim_route_setup())
|
||||
.route("/robots.txt", get(robots_txt))
|
||||
.nest("/v1", v1::router(state.clone()))
|
||||
// Shared account features only - mainly this is for unix-like features.
|
||||
.route("/status", get(status));
|
||||
.route("/status", get(status))
|
||||
.merge(oauth2::oauth2_route_setup(state.clone()))
|
||||
.merge(v1_scim::scim_route_setup())
|
||||
.merge(v1::router(state.clone()));
|
||||
|
||||
let app = match config.role {
|
||||
ServerRole::WriteReplicaNoUI => app,
|
||||
ServerRole::WriteReplica | ServerRole::ReadOnlyReplica => {
|
||||
|
@ -250,11 +250,14 @@ pub async fn create_https_server(
|
|||
middleware::csp_headers::cspheaders_layer,
|
||||
))
|
||||
.layer(from_fn(middleware::version_middleware))
|
||||
.layer(from_fn(middleware::kopid_end))
|
||||
.layer(from_fn(middleware::kopid_start))
|
||||
.layer(session_layer)
|
||||
.with_state(state)
|
||||
.layer(TraceLayer::new_for_http())
|
||||
// This must be the LAST middleware.
|
||||
// This is because the last middleware here is the first to be entered and the last
|
||||
// to be exited, and this middleware sets up ids' and other bits for for logging
|
||||
// coherence to be maintained.
|
||||
.layer(from_fn(middleware::kopid_middleware))
|
||||
.with_state(state)
|
||||
// the connect_info bit here lets us pick up the remote address of the client
|
||||
.into_make_service_with_connect_info::<SocketAddr>();
|
||||
|
||||
|
@ -301,7 +304,7 @@ async fn server_loop(
|
|||
let mut tls_builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?;
|
||||
let mut app = app;
|
||||
tls_builder
|
||||
.set_certificate_file(tls_param.chain.clone(), SslFiletype::PEM)
|
||||
.set_certificate_chain_file(tls_param.chain.clone())
|
||||
.map_err(|err| {
|
||||
std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
|
|
|
@ -59,7 +59,6 @@ pub async fn oauth2_id_get(
|
|||
Path(rs_name): Path<String>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
) -> impl IntoResponse {
|
||||
|
||||
let filter = oauth2_id(&rs_name);
|
||||
|
||||
let res = state
|
||||
|
@ -227,8 +226,6 @@ pub async fn oauth2_id_delete(
|
|||
// valid Kanidm instance in the topology can handle these request.
|
||||
//
|
||||
|
||||
|
||||
|
||||
pub async fn oauth2_authorise_post(
|
||||
State(state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
|
@ -349,8 +346,8 @@ async fn oauth2_authorise(
|
|||
// we do NOT redirect in an error condition, and just render the error ourselves.
|
||||
Err(e) => {
|
||||
admin_error!(
|
||||
"Unable to authorise - Error ID: {} error: {}",
|
||||
&kopid.eventid_value(),
|
||||
"Unable to authorise - Error ID: {:?} error: {}",
|
||||
kopid.eventid,
|
||||
&e.to_string()
|
||||
);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
|
@ -514,7 +511,7 @@ async fn oauth2_authorise_reject(
|
|||
pub async fn oauth2_token_post(
|
||||
State(state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
headers: HeaderMap, // TOOD: make this a typed basic auth header
|
||||
headers: HeaderMap, // TODO: make this a typed basic auth header
|
||||
Form(tok_req): Form<AccessTokenRequest>,
|
||||
) -> impl IntoResponse {
|
||||
// This is called directly by the resource server, where we then issue
|
||||
|
@ -834,52 +831,58 @@ pub async fn oauth2_token_revoke_post(
|
|||
|
||||
pub fn oauth2_route_setup(state: ServerState) -> Router<ServerState> {
|
||||
// this has all the openid-related routes
|
||||
let openid_router = Router::new() // appserver.at("/oauth2/openid");
|
||||
let openid_router = Router::new()
|
||||
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route(
|
||||
"/:client_id/.well-known/openid-configuration",
|
||||
"/oauth2/openid/:client_id/.well-known/openid-configuration",
|
||||
get(oauth2_openid_discovery_get),
|
||||
)
|
||||
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route("/:client_id/userinfo", get(oauth2_openid_userinfo_get))
|
||||
.route(
|
||||
"/oauth2/openid/:client_id/userinfo",
|
||||
get(oauth2_openid_userinfo_get),
|
||||
)
|
||||
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route(
|
||||
"/:client_id/public_key.jwk",
|
||||
"/oauth2/openid/:client_id/public_key.jwk",
|
||||
get(oauth2_openid_publickey_get),
|
||||
)
|
||||
.with_state(state.clone());
|
||||
|
||||
Router::new() //= appserver.at("/oauth2");
|
||||
.route("/", get(oauth2_get))
|
||||
Router::new()
|
||||
.route("/oauth2", get(oauth2_get))
|
||||
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route(
|
||||
"/authorise",
|
||||
"/oauth2/authorise",
|
||||
post(oauth2_authorise_post).get(oauth2_authorise_get),
|
||||
)
|
||||
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route(
|
||||
"/authorise/permit",
|
||||
"/oauth2/authorise/permit",
|
||||
post(oauth2_authorise_permit_post).get(oauth2_authorise_permit_get),
|
||||
)
|
||||
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route(
|
||||
"/authorise/reject",
|
||||
"/oauth2/authorise/reject",
|
||||
post(oauth2_authorise_reject_post).get(oauth2_authorise_reject_get),
|
||||
)
|
||||
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route("/token", post(oauth2_token_post))
|
||||
.route("/oauth2/token", post(oauth2_token_post))
|
||||
// ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route("/token/introspect", post(oauth2_token_introspect_post))
|
||||
.route("/token/revoke", post(oauth2_token_revoke_post))
|
||||
.nest("/openid", openid_router)
|
||||
.route(
|
||||
"/oauth2/token/introspect",
|
||||
post(oauth2_token_introspect_post),
|
||||
)
|
||||
.route("/oauth2/token/revoke", post(oauth2_token_revoke_post))
|
||||
.merge(openid_router)
|
||||
.with_state(state)
|
||||
.layer(from_fn(super::middleware::caching::dont_cache_me))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use std::net::SocketAddr;
|
||||
#[allow(unused_imports)]
|
||||
// //! The V1 API things!
|
||||
use std::str::FromStr;
|
||||
//! The V1 API things!
|
||||
|
||||
use axum::extract::{ConnectInfo, Path, Query, State};
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::headers::{CacheControl, HeaderMapExt};
|
||||
use axum::middleware::from_fn;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
|
@ -29,6 +26,7 @@ use kanidmd_lib::value::PartialValue;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::https::extractors::TrustedClientIp;
|
||||
use crate::https::to_axum_response;
|
||||
|
||||
use super::middleware::caching::dont_cache_me;
|
||||
|
@ -1244,24 +1242,15 @@ pub async fn applinks_get(
|
|||
|
||||
pub async fn reauth(
|
||||
State(state): State<ServerState>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>, // TODO: test x-ff-headers
|
||||
TrustedClientIp(ip_addr): TrustedClientIp,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
session: WritableSession,
|
||||
Json(obj): Json<AuthIssueSession>,
|
||||
) -> impl IntoResponse {
|
||||
// TODO: xff things check that we can get the remote IP address first, since this doesn't touch the backend at all
|
||||
// let ip_addr = req.get_remote_addr().ok_or_else(|| {
|
||||
// error!("Unable to get remote addr for auth event, refusing to proceed");
|
||||
// tide::Error::from_str(
|
||||
// tide::StatusCode::InternalServerError,
|
||||
// "unable to validate peer address",
|
||||
// )
|
||||
// })?;
|
||||
|
||||
// This may change in the future ...
|
||||
let inter = state
|
||||
.qe_r_ref
|
||||
.handle_reauth(kopid.uat, obj, kopid.eventid, addr.ip())
|
||||
.handle_reauth(kopid.uat, obj, kopid.eventid, ip_addr)
|
||||
.await;
|
||||
debug!("REAuth result: {:?}", inter);
|
||||
auth_session_state_management(state, inter, session)
|
||||
|
@ -1269,28 +1258,16 @@ pub async fn reauth(
|
|||
|
||||
pub async fn auth(
|
||||
State(state): State<ServerState>,
|
||||
TrustedClientIp(ip_addr): TrustedClientIp,
|
||||
session: WritableSession,
|
||||
headers: HeaderMap,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
Json(obj): Json<AuthRequest>,
|
||||
) -> impl IntoResponse {
|
||||
// TODO: check this trusts the x-ff-header
|
||||
let ip_addr = addr.ip();
|
||||
// check that we can get the remote IP address first, since this doesn't touch the backend at all
|
||||
// let ip_addr = req.get_remote_addr().ok_or_else(|| {
|
||||
// error!("Unable to get remote addr for auth event, refusing to proceed");
|
||||
// tide::Error::from_str(
|
||||
// tide::StatusCode::InternalServerError,
|
||||
// "unable to validate peer address",
|
||||
// )
|
||||
// })?;
|
||||
|
||||
// First, deal with some state management.
|
||||
// Do anything here first that's needed like getting the session details
|
||||
// out of the req cookie.
|
||||
|
||||
// TODO
|
||||
let maybe_sessionid = state.get_current_auth_session_id(&headers, &session);
|
||||
debug!("Session ID: {:?}", maybe_sessionid);
|
||||
// We probably need to know if we allocate the cookie, that this is a
|
||||
|
@ -1434,281 +1411,295 @@ pub async fn auth_valid(
|
|||
#[instrument(skip(state))]
|
||||
pub fn router(state: ServerState) -> Router<ServerState> {
|
||||
Router::new()
|
||||
.route("/oauth2", get(super::oauth2::oauth2_get))
|
||||
.route("/v1/oauth2", get(super::oauth2::oauth2_get))
|
||||
.route(
|
||||
"/oauth2/:rs_name",
|
||||
"/v1/oauth2/:rs_name",
|
||||
get(super::oauth2::oauth2_id_get)
|
||||
.patch(super::oauth2::oauth2_id_patch)
|
||||
.delete(super::oauth2::oauth2_id_delete),
|
||||
)
|
||||
.route(
|
||||
"/oauth2/:rs_name/_basic_secret",
|
||||
"/v1/oauth2/:rs_name/_basic_secret",
|
||||
get(super::oauth2::oauth2_id_get_basic_secret),
|
||||
)
|
||||
.route("/oauth2/_basic", post(super::oauth2::oauth2_basic_post))
|
||||
.route("/v1/oauth2/_basic", post(super::oauth2::oauth2_basic_post))
|
||||
.route(
|
||||
"/oauth2/:rs_name/_scopemap/:group",
|
||||
"/v1/oauth2/:rs_name/_scopemap/:group",
|
||||
post(super::oauth2::oauth2_id_scopemap_post)
|
||||
.delete(super::oauth2::oauth2_id_scopemap_delete),
|
||||
)
|
||||
.route(
|
||||
"/oauth2/:rs_name/_sup_scopemap/:group",
|
||||
"/v1/oauth2/:rs_name/_sup_scopemap/:group",
|
||||
post(super::oauth2::oauth2_id_sup_scopemap_post)
|
||||
.delete(super::oauth2::oauth2_id_sup_scopemap_delete),
|
||||
)
|
||||
.route("/raw/create", post(create))
|
||||
.route("/raw/modify", post(v1_modify))
|
||||
.route("/raw/delete", post(v1_delete))
|
||||
.route("/raw/search", post(search))
|
||||
.route("/schema", get(schema_get))
|
||||
.route("/v1/raw/create", post(create))
|
||||
.route("/v1/raw/modify", post(v1_modify))
|
||||
.route("/v1/raw/delete", post(v1_delete))
|
||||
.route("/v1/raw/search", post(search))
|
||||
.route("/v1/schema", get(schema_get))
|
||||
.route(
|
||||
"/schema/attributetype",
|
||||
"/v1/schema/attributetype",
|
||||
get(schema_attributetype_get), // post(|| async { "TODO" })
|
||||
)
|
||||
.route(
|
||||
"/schema/attributetype/:id",
|
||||
"/v1/schema/attributetype/:id",
|
||||
get(schema_attributetype_get_id),
|
||||
)
|
||||
// .route("/schema/attributetype/:id", put(|| async { "TODO" }).patch(|| async { "TODO" }))
|
||||
.route(
|
||||
"/schema/classtype",
|
||||
"/v1/schema/classtype",
|
||||
get(schema_classtype_get), // .post(|| async { "TODO" })
|
||||
)
|
||||
.route(
|
||||
"/schema/classtype/:id",
|
||||
"/v1/schema/classtype/:id",
|
||||
get(schema_classtype_get_id)
|
||||
.put(|| async { "TODO" })
|
||||
.patch(|| async { "TODO" }),
|
||||
)
|
||||
.route("/self", get(whoami))
|
||||
.route("/self/_uat", get(whoami_uat))
|
||||
.route("/self/_attr/:attr", get(|| async { "TODO" }))
|
||||
.route("/self/_credential", get(|| async { "TODO" }))
|
||||
.route("/self/_credential/:cid/_lock", get(|| async { "TODO" }))
|
||||
.route("/v1/self", get(whoami))
|
||||
.route("/v1/self/_uat", get(whoami_uat))
|
||||
.route("/v1/self/_attr/:attr", get(|| async { "TODO" }))
|
||||
.route("/v1/self/_credential", get(|| async { "TODO" }))
|
||||
.route("/v1/self/_credential/:cid/_lock", get(|| async { "TODO" }))
|
||||
.route(
|
||||
"/self/_radius",
|
||||
"/v1/self/_radius",
|
||||
get(|| async { "TODO" })
|
||||
.delete(|| async { "TODO" })
|
||||
.post(|| async { "TODO" }),
|
||||
)
|
||||
.route("/self/_radius/_config", post(|| async { "TODO" }))
|
||||
.route("/self/_radius/_config/:token", get(|| async { "TODO" }))
|
||||
.route("/v1/self/_radius/_config", post(|| async { "TODO" }))
|
||||
.route("/v1/self/_radius/_config/:token", get(|| async { "TODO" }))
|
||||
.route(
|
||||
"/self/_radius/_config/:token/apple",
|
||||
"/v1/self/_radius/_config/:token/apple",
|
||||
get(|| async { "TODO" }),
|
||||
)
|
||||
// Applinks are the list of apps this account can access.
|
||||
.route("/self/_applinks", get(applinks_get))
|
||||
.route("/v1/self/_applinks", get(applinks_get))
|
||||
// Person routes
|
||||
.route("/person", get(person_get))
|
||||
.route("/person", post(person_post))
|
||||
.route("/v1/person", get(person_get))
|
||||
.route("/v1/person", post(person_post))
|
||||
.route(
|
||||
"/person/:id",
|
||||
"/v1/person/:id",
|
||||
get(person_id_get)
|
||||
.patch(account_id_patch)
|
||||
.delete(person_account_id_delete),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_attr/:attr",
|
||||
"/v1/person/:id/_attr/:attr",
|
||||
get(account_id_get_attr)
|
||||
.put(account_id_put_attr)
|
||||
.post(account_id_post_attr)
|
||||
.delete(account_id_delete_attr),
|
||||
)
|
||||
.route("/person/:id/_lock", get(|| async { "TODO" }))
|
||||
.route("/person/:id/_credential", get(|| async { "TODO" }))
|
||||
.route("/v1/person/:id/_lock", get(|| async { "TODO" }))
|
||||
.route("/v1/person/:id/_credential", get(|| async { "TODO" }))
|
||||
.route(
|
||||
"/person/:id/_credential/_status",
|
||||
"/v1/person/:id/_credential/_status",
|
||||
get(account_get_id_credential_status),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_credential/:cid/_lock",
|
||||
"/v1/person/:id/_credential/:cid/_lock",
|
||||
get(|| async { "TODO" }),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_credential/_update",
|
||||
"/v1/person/:id/_credential/_update",
|
||||
get(account_get_id_credential_update),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_credential/_update_intent/:ttl",
|
||||
"/v1/person/:id/_credential/_update_intent/:ttl",
|
||||
get(account_get_id_credential_update_intent_ttl),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_credential/_update_intent",
|
||||
"/v1/person/:id/_credential/_update_intent",
|
||||
get(account_get_id_credential_update_intent),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_ssh_pubkeys",
|
||||
"/v1/person/:id/_ssh_pubkeys",
|
||||
get(account_get_id_ssh_pubkeys).post(account_post_id_ssh_pubkey),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_ssh_pubkeys/:tag",
|
||||
"/v1/person/:id/_ssh_pubkeys/:tag",
|
||||
get(account_get_id_ssh_pubkey_tag).delete(account_delete_id_ssh_pubkey_tag),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_radius",
|
||||
"/v1/person/:id/_radius",
|
||||
get(account_get_id_radius)
|
||||
.post(account_post_id_radius_regenerate)
|
||||
.delete(account_delete_id_radius),
|
||||
)
|
||||
.route(
|
||||
"/person/:id/_radius/_token",
|
||||
"/v1/person/:id/_radius/_token",
|
||||
get(account_get_id_radius_token),
|
||||
) // TODO: make this cacheable
|
||||
.route("/person/:id/_unix", post(account_post_id_unix))
|
||||
.route("/v1/person/:id/_unix", post(account_post_id_unix))
|
||||
.route(
|
||||
"/person/:id/_unix/_credential",
|
||||
"/v1/person/:id/_unix/_credential",
|
||||
put(account_put_id_unix_credential).delete(account_delete_id_unix_credential),
|
||||
)
|
||||
// Service accounts
|
||||
.route(
|
||||
"/service_account",
|
||||
"/v1/service_account",
|
||||
get(service_account_get).post(service_account_post),
|
||||
)
|
||||
.route(
|
||||
"/service_account/",
|
||||
"/v1/service_account/",
|
||||
get(service_account_get).post(service_account_post),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id",
|
||||
"/v1/service_account/:id",
|
||||
get(service_account_id_get).delete(service_account_id_delete),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id/_attr/:attr",
|
||||
"/v1/service_account/:id/_attr/:attr",
|
||||
get(account_id_get_attr)
|
||||
.put(account_id_put_attr)
|
||||
.post(account_id_post_attr)
|
||||
.delete(account_id_delete_attr),
|
||||
)
|
||||
.route("/service_account/:id/_lock", get(|| async { "TODO" }))
|
||||
.route("/v1/service_account/:id/_lock", get(|| async { "TODO" }))
|
||||
.route(
|
||||
"/service_account/:id/_into_person",
|
||||
"/v1/service_account/:id/_into_person",
|
||||
post(service_account_into_person),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id/_api_token",
|
||||
"/v1/service_account/:id/_api_token",
|
||||
post(service_account_api_token_post).get(service_account_api_token_get),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id/_api_token/:token_id",
|
||||
"/v1/service_account/:id/_api_token/:token_id",
|
||||
delete(service_account_api_token_delete),
|
||||
)
|
||||
.route("/service_account/:id/_credential", get(|| async { "TODO" }))
|
||||
.route(
|
||||
"/service_account/:id/_credential/_generate",
|
||||
get(service_account_credential_generate),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id/_credential/_status",
|
||||
get(account_get_id_credential_status),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id/_credential/:cid/_lock",
|
||||
"/v1/service_account/:id/_credential",
|
||||
get(|| async { "TODO" }),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id/_ssh_pubkeys",
|
||||
"/v1/service_account/:id/_credential/_generate",
|
||||
get(service_account_credential_generate),
|
||||
)
|
||||
.route(
|
||||
"/v1/service_account/:id/_credential/_status",
|
||||
get(account_get_id_credential_status),
|
||||
)
|
||||
.route(
|
||||
"/v1/service_account/:id/_credential/:cid/_lock",
|
||||
get(|| async { "TODO" }),
|
||||
)
|
||||
.route(
|
||||
"/v1/service_account/:id/_ssh_pubkeys",
|
||||
get(account_get_id_ssh_pubkeys).post(account_post_id_ssh_pubkey),
|
||||
)
|
||||
.route(
|
||||
"/service_account/:id/_ssh_pubkeys/:tag",
|
||||
"/v1/service_account/:id/_ssh_pubkeys/:tag",
|
||||
get(account_get_id_ssh_pubkey_tag).delete(account_delete_id_ssh_pubkey_tag),
|
||||
)
|
||||
.route("/service_account/:id/_unix", post(account_post_id_unix))
|
||||
.route("/account/:id/_unix/_auth", post(account_post_id_unix_auth))
|
||||
.route("/v1/service_account/:id/_unix", post(account_post_id_unix))
|
||||
.route(
|
||||
"/account/:id/_unix/_token",
|
||||
"/v1/account/:id/_unix/_auth",
|
||||
post(account_post_id_unix_auth),
|
||||
)
|
||||
.route(
|
||||
"/v1/account/:id/_unix/_token",
|
||||
post(account_get_id_unix_token).get(account_get_id_unix_token), // TODO: make this cacheable
|
||||
)
|
||||
.route(
|
||||
"/account/:id/_radius/_token",
|
||||
"/v1/account/:id/_radius/_token",
|
||||
post(account_get_id_radius_token).get(account_get_id_radius_token), // TODO: make this cacheable
|
||||
)
|
||||
.route("/account/:id/_ssh_pubkeys", get(account_get_id_ssh_pubkeys))
|
||||
.route(
|
||||
"/account/:id/_ssh_pubkeys/:tag",
|
||||
"/v1/account/:id/_ssh_pubkeys",
|
||||
get(account_get_id_ssh_pubkeys),
|
||||
)
|
||||
.route(
|
||||
"/v1/account/:id/_ssh_pubkeys/:tag",
|
||||
get(account_get_id_ssh_pubkey_tag),
|
||||
)
|
||||
.route(
|
||||
"/account/:id/_user_auth_token",
|
||||
"/v1/account/:id/_user_auth_token",
|
||||
get(account_get_id_user_auth_token),
|
||||
)
|
||||
.route(
|
||||
"/account/:id/_user_auth_token/:token_id",
|
||||
"/v1/account/:id/_user_auth_token/:token_id",
|
||||
delete(account_user_auth_token_delete),
|
||||
)
|
||||
.route(
|
||||
"/credential/_exchange_intent",
|
||||
"/v1/credential/_exchange_intent",
|
||||
post(credential_update_exchange_intent),
|
||||
)
|
||||
.route("/credential/_status", post(credential_update_status))
|
||||
.route("/credential/_update", post(credential_update_update))
|
||||
.route("/credential/_commit", post(credential_update_commit))
|
||||
.route("/credential/_cancel", post(credential_update_cancel))
|
||||
.route("/v1/credential/_status", post(credential_update_status))
|
||||
.route("/v1/credential/_update", post(credential_update_update))
|
||||
.route("/v1/credential/_commit", post(credential_update_commit))
|
||||
.route("/v1/credential/_cancel", post(credential_update_cancel))
|
||||
// domain-things
|
||||
.route("/domain", get(domain_get))
|
||||
.route("/v1/domain", get(domain_get))
|
||||
.route(
|
||||
"/domain/_attr/:attr",
|
||||
"/v1/domain/_attr/:attr",
|
||||
get(domain_get_attr)
|
||||
.put(domain_put_attr)
|
||||
.delete(domain_delete_attr),
|
||||
)
|
||||
.route("/group/:id/_unix/_token", get(group_get_id_unix_token))
|
||||
.route("/group/:id/_unix", post(group_post_id_unix))
|
||||
.route("/group", get(group_get).post(group_post))
|
||||
.route("/group/:id", get(group_id_get).delete(group_id_delete))
|
||||
.route("/v1/group/:id/_unix/_token", get(group_get_id_unix_token))
|
||||
.route("/v1/group/:id/_unix", post(group_post_id_unix))
|
||||
.route("/v1/group", get(group_get).post(group_post))
|
||||
.route("/v1/group/:id", get(group_id_get).delete(group_id_delete))
|
||||
.route(
|
||||
"/group/:id/_attr/:attr",
|
||||
"/v1/group/:id/_attr/:attr",
|
||||
delete(group_id_delete_attr)
|
||||
.get(group_id_get_attr)
|
||||
.put(group_id_put_attr)
|
||||
.post(group_id_post_attr),
|
||||
)
|
||||
.with_state(state.clone())
|
||||
.route("/system", get(system_get))
|
||||
.route("/v1/system", get(system_get))
|
||||
.route(
|
||||
"/system/_attr/:attr",
|
||||
"/v1/system/_attr/:attr",
|
||||
get(system_get_attr)
|
||||
.post(system_post_attr)
|
||||
.delete(system_delete_attr),
|
||||
)
|
||||
.route("/recycle_bin", get(recycle_bin_get))
|
||||
.route("/recycle_bin/:id", get(recycle_bin_id_get))
|
||||
.route("/recycle_bin/:id/_revive", post(recycle_bin_revive_id_post))
|
||||
.route("/access_profile", get(|| async { "TODO" }))
|
||||
.route("/access_profile/:id", get(|| async { "TODO" }))
|
||||
.route("/access_profile/:id/_attr/:attr", get(|| async { "TODO" }))
|
||||
.route("/auth", post(auth))
|
||||
.route("/auth/valid", get(auth_valid))
|
||||
.route("/logout", get(logout))
|
||||
.route("/reauth", post(reauth))
|
||||
.route("/v1/recycle_bin", get(recycle_bin_get))
|
||||
.route("/v1/recycle_bin/:id", get(recycle_bin_id_get))
|
||||
.route(
|
||||
"/sync_account",
|
||||
"/v1/recycle_bin/:id/_revive",
|
||||
post(recycle_bin_revive_id_post),
|
||||
)
|
||||
.route("/v1/access_profile", get(|| async { "TODO" }))
|
||||
.route("/v1/access_profile/:id", get(|| async { "TODO" }))
|
||||
.route(
|
||||
"/v1/access_profile/:id/_attr/:attr",
|
||||
get(|| async { "TODO" }),
|
||||
)
|
||||
.route("/v1/auth", post(auth))
|
||||
.route("/v1/auth/valid", get(auth_valid))
|
||||
.route("/v1/logout", get(logout))
|
||||
.route("/v1/reauth", post(reauth))
|
||||
.route(
|
||||
"/v1/sync_account",
|
||||
get(sync_account_get).post(sync_account_post),
|
||||
)
|
||||
.route(
|
||||
"/sync_account/",
|
||||
"/v1/sync_account/",
|
||||
get(sync_account_get).post(sync_account_post),
|
||||
)
|
||||
.route(
|
||||
"/sync_account/:id",
|
||||
"/v1/sync_account/:id",
|
||||
get(sync_account_id_get).patch(sync_account_id_patch),
|
||||
)
|
||||
.route(
|
||||
"/sync_account/:id/_attr/:attr",
|
||||
"/v1/sync_account/:id/_attr/:attr",
|
||||
get(sync_account_id_get_attr).put(sync_account_id_put_attr),
|
||||
)
|
||||
.route(
|
||||
"/sync_account/:id/_finalise",
|
||||
"/v1/sync_account/:id/_finalise",
|
||||
get(sync_account_id_get_finalise),
|
||||
)
|
||||
.route(
|
||||
"/sync_account/:id/_terminate",
|
||||
"/v1/sync_account/:id/_terminate",
|
||||
get(sync_account_id_get_terminate),
|
||||
)
|
||||
.route(
|
||||
"/sync_account/:id/_sync_token",
|
||||
// .get(&mut sync_account_token_get)
|
||||
"/v1/sync_account/:id/_sync_token",
|
||||
post(sync_account_token_post).delete(sync_account_token_delete),
|
||||
)
|
||||
.with_state(state)
|
||||
|
|
|
@ -254,6 +254,6 @@ pub fn scim_route_setup() -> Router<ServerState> {
|
|||
//
|
||||
// POST Send a sync update
|
||||
//
|
||||
.route("/v1/Sync", post(scim_sync_post).get(scim_sync_get))
|
||||
.route("/v1/Sink", get(scim_sink_get))
|
||||
.route("/scim/v1/Sync", post(scim_sync_post).get(scim_sync_get))
|
||||
.route("/scim/v1/Sink", get(scim_sink_get))
|
||||
}
|
||||
|
|
|
@ -762,7 +762,7 @@ pub async fn create_server_core(
|
|||
}
|
||||
};
|
||||
|
||||
let cookie_key: [u8; 32] = idms.get_cookie_key();
|
||||
let cookie_key: [u8; 64] = idms.get_cookie_key();
|
||||
|
||||
// Any pre-start tasks here.
|
||||
match &config.integration_test_config {
|
||||
|
|
|
@ -126,21 +126,15 @@ async fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
// if they specified it in the environment then that overrides everything
|
||||
let log_filter = match EnvFilter::try_from_default_env() {
|
||||
Ok(val) => val,
|
||||
Err(_e) => {
|
||||
// we couldn't get it from the env, so we'll try the config file!
|
||||
match sconfig.as_ref() {
|
||||
Some(val) => {
|
||||
let tmp = val.log_level.clone();
|
||||
tmp.unwrap_or_default()
|
||||
}
|
||||
None => LogLevel::Info,
|
||||
}
|
||||
.into()
|
||||
// We only allow config file for log level now.
|
||||
let log_filter: EnvFilter = match sconfig.as_ref() {
|
||||
Some(val) => {
|
||||
let tmp = val.log_level.clone();
|
||||
tmp.unwrap_or_default()
|
||||
}
|
||||
};
|
||||
None => LogLevel::Info,
|
||||
}
|
||||
.into();
|
||||
|
||||
// TODO: only send to stderr when we're not in a TTY
|
||||
tracing_forest::worker_task()
|
||||
|
|
|
@ -585,7 +585,7 @@ lazy_static! {
|
|||
"description",
|
||||
Value::new_utf8s("System (local) info and metadata object.")
|
||||
),
|
||||
("version", Value::Uint32(12))
|
||||
("version", Value::Uint32(13))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ pub struct DomainKeys {
|
|||
pub(crate) uat_jwt_signer: JwsSigner,
|
||||
pub(crate) uat_jwt_validator: JwsValidator,
|
||||
pub(crate) token_enc_key: Fernet,
|
||||
pub(crate) cookie_key: [u8; 32],
|
||||
pub(crate) cookie_key: [u8; 64],
|
||||
}
|
||||
|
||||
pub struct IdmServer {
|
||||
|
@ -262,7 +262,7 @@ impl IdmServer {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn get_cookie_key(&self) -> [u8; 32] {
|
||||
pub fn get_cookie_key(&self) -> [u8; 64] {
|
||||
self.domain_keys.read().cookie_key
|
||||
}
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ impl Domain {
|
|||
|
||||
if !e.attribute_pres("private_cookie_key") {
|
||||
security_info!("regenerating domain cookie key");
|
||||
let mut key = [0; 32];
|
||||
let mut key = [0; 64];
|
||||
let mut rng = StdRng::from_entropy();
|
||||
rng.fill(&mut key);
|
||||
let v = Value::new_privatebinary(&key);
|
||||
|
|
|
@ -99,6 +99,10 @@ impl QueryServer {
|
|||
if system_info_version < 12 {
|
||||
write_txn.migrate_11_to_12()?;
|
||||
}
|
||||
|
||||
if system_info_version < 13 {
|
||||
write_txn.migrate_12_to_13()?;
|
||||
}
|
||||
}
|
||||
|
||||
write_txn.reload()?;
|
||||
|
@ -391,6 +395,19 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
self.internal_apply_writable(mod_candidates)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn migrate_12_to_13(&mut self) -> Result<(), OperationError> {
|
||||
admin_warn!("starting 12 to 13 migration.");
|
||||
let filter = filter!(f_and!([
|
||||
f_eq("class", PVCLASS_DOMAIN_INFO.clone()),
|
||||
f_eq("uuid", PVUUID_DOMAIN_INFO.clone()),
|
||||
]));
|
||||
// Delete the existing cookie key to trigger a regeneration.
|
||||
let modlist = ModifyList::new_purge("private_cookie_key");
|
||||
self.internal_modify(&filter, &modlist)
|
||||
// Complete
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
|
||||
admin_debug!("initialise_schema_core -> start ...");
|
||||
|
|
|
@ -763,12 +763,12 @@ pub trait QueryServerTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn get_domain_cookie_key(&mut self) -> Result<[u8; 32], OperationError> {
|
||||
fn get_domain_cookie_key(&mut self) -> Result<[u8; 64], OperationError> {
|
||||
self.internal_search_uuid(UUID_DOMAIN_INFO)
|
||||
.and_then(|e| {
|
||||
e.get_ava_single_private_binary("private_cookie_key")
|
||||
.and_then(|s| {
|
||||
let mut x = [0; 32];
|
||||
let mut x = [0; 64];
|
||||
if s.len() == x.len() {
|
||||
x.copy_from_slice(s);
|
||||
Some(x)
|
||||
|
|
Loading…
Reference in a new issue