Unverified Commit c6bfb507 authored by Adam C. Stephens's avatar Adam C. Stephens
Browse files

kanidm_1_9: init at 1.9.0-pre

parent d6f0ad1c
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
import ./generic.nix {
  version = "1.9.0-pre";
  hash = "sha256-p+dZWG9czUtK7IaifM/1CbXfpLhKPBflL/uDqHtHwHA=";
  cargoHash = "sha256-razlbe5VEiWz427dShvWT/rVuvBh5Re/z1vXsVQGOgM=";
}
+7 −0
Original line number Diff line number Diff line
@@ -85,11 +85,18 @@ rustPlatform.buildRustPackage (finalAttrs: {
      }
      // lib.optionalAttrs (lib.versionAtLeast finalAttrs.version "1.8") {
        resolver_service_account_token_path = "/etc/kanidm/token";
      }
      // lib.optionalAttrs (lib.versionAtLeast finalAttrs.version "1.9") {
        server_migration_path = "/etc/kanidm/migrations.d";
      };
    in
    ''
      cp ${format profile} libs/profiles/${finalAttrs.env.KANIDM_BUILD_PROFILE}.toml
      substituteInPlace libs/profiles/${finalAttrs.env.KANIDM_BUILD_PROFILE}.toml --replace-fail '@htmx_ui_pkg_path@' "$out/ui/hpkg"
    ''
    + lib.optionalString (lib.versionAtLeast finalAttrs.version "1.9") ''
      substituteInPlace Cargo.toml \
        --replace-fail 'rust-version = "1.93"' 'rust-version = "1.91"'
    '';

  nativeBuildInputs = [
+159 −0
Original line number Diff line number Diff line
From bebd0ae51344eba2bc9bb8e8bd88f279daf09581 Mon Sep 17 00:00:00 2001
From: oddlama <oddlama@oddlama.org>
Date: Mon, 10 Nov 2025 19:58:39 +0100
Subject: [PATCH 1/2] oauth2 basic secret modify

---
 server/core/src/actors/v1_write.rs  | 42 +++++++++++++++++++++++++++++
 server/core/src/https/v1.rs         |  6 ++++-
 server/core/src/https/v1_oauth2.rs  | 29 ++++++++++++++++++++
 server/lib/src/server/migrations.rs | 16 +++++++++++
 4 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs
index 732e826c8..a2b8e503f 100644
--- a/server/core/src/actors/v1_write.rs
+++ b/server/core/src/actors/v1_write.rs
@@ -324,6 +324,48 @@ impl QueryServerWriteV1 {
             .and_then(|_| idms_prox_write.commit().map(|_| ()))
     }
 
+    #[instrument(
+        level = "info",
+        skip_all,
+        fields(uuid = ?eventid)
+    )]
+    pub async fn handle_oauth2_basic_secret_write(
+        &self,
+        client_auth_info: ClientAuthInfo,
+        filter: Filter<FilterInvalid>,
+        new_secret: String,
+        eventid: Uuid,
+    ) -> Result<(), OperationError> {
+        // Given a protoEntry, turn this into a modification set.
+        let ct = duration_from_epoch_now();
+        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
+        let ident = idms_prox_write
+            .validate_client_auth_info_to_ident(client_auth_info, ct)
+            .map_err(|e| {
+                admin_error!(err = ?e, "Invalid identity");
+                e
+            })?;
+
+        let modlist = ModifyList::new_purge_and_set(
+            Attribute::OAuth2RsBasicSecret,
+            Value::SecretValue(new_secret),
+        );
+
+        let mdf =
+            ModifyEvent::from_internal_parts(ident, &modlist, &filter, &idms_prox_write.qs_write)
+                .map_err(|e| {
+                admin_error!(err = ?e, "Failed to begin modify during handle_oauth2_basic_secret_write");
+                e
+            })?;
+
+        trace!(?mdf, "Begin modify event");
+
+        idms_prox_write
+            .qs_write
+            .modify(&mdf)
+            .and_then(|_| idms_prox_write.commit())
+    }
+
     #[instrument(
         level = "info",
         skip_all,
diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs
index 7d5beb1f0..210147e0a 100644
--- a/server/core/src/https/v1.rs
+++ b/server/core/src/https/v1.rs
@@ -10,7 +10,7 @@ use axum::extract::{Path, State};
 use axum::http::{HeaderMap, HeaderValue};
 use axum::middleware::from_fn;
 use axum::response::{IntoResponse, Response};
-use axum::routing::{delete, get, post, put};
+use axum::routing::{delete, get, post, put, patch};
 use axum::{Extension, Json, Router};
 use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
 use compact_jwt::{Jwk, Jws, JwsSigner};
@@ -3113,6 +3113,10 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
             "/v1/oauth2/{rs_name}/_basic_secret",
             get(super::v1_oauth2::oauth2_id_get_basic_secret),
         )
+        .route(
+            "/v1/oauth2/{rs_name}/_basic_secret",
+            patch(super::v1_oauth2::oauth2_id_patch_basic_secret),
+        )
         .route(
             "/v1/oauth2/{rs_name}/_scopemap/{group}",
             post(super::v1_oauth2::oauth2_id_scopemap_post)
diff --git a/server/core/src/https/v1_oauth2.rs b/server/core/src/https/v1_oauth2.rs
index f399539bc..ffad9921e 100644
--- a/server/core/src/https/v1_oauth2.rs
+++ b/server/core/src/https/v1_oauth2.rs
@@ -151,6 +151,35 @@ pub(crate) async fn oauth2_id_get_basic_secret(
         .map_err(WebError::from)
 }
 
+#[utoipa::path(
+    patch,
+    path = "/v1/oauth2/{rs_name}/_basic_secret",
+    request_body=ProtoEntry,
+    responses(
+        DefaultApiResponse,
+    ),
+    security(("token_jwt" = [])),
+    tag = "v1/oauth2",
+    operation_id = "oauth2_id_patch_basic_secret"
+)]
+/// Overwrite the basic secret for a given OAuth2 Resource Server.
+#[instrument(level = "info", skip(state, new_secret))]
+pub(crate) async fn oauth2_id_patch_basic_secret(
+    State(state): State<ServerState>,
+    Extension(kopid): Extension<KOpId>,
+    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
+    Path(rs_name): Path<String>,
+    Json(new_secret): Json<String>,
+) -> Result<Json<()>, WebError> {
+    let filter = oauth2_id(&rs_name);
+    state
+        .qe_w_ref
+        .handle_oauth2_basic_secret_write(client_auth_info, filter, new_secret, kopid.eventid)
+        .await
+        .map(Json::from)
+        .map_err(WebError::from)
+}
+
 #[utoipa::path(
     patch,
     path = "/v1/oauth2/{rs_name}",
diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs
index a916eced2..94327e938 100644
--- a/server/lib/src/server/migrations.rs
+++ b/server/lib/src/server/migrations.rs
@@ -172,6 +172,22 @@ impl QueryServer {
             reload_required = true;
         };
 
+        // secret provisioning: allow idm_admin to modify OAuth2RsBasicSecret.
+        write_txn.internal_modify_uuid(
+            UUID_IDM_ACP_OAUTH2_MANAGE_V1,
+            &ModifyList::new_append(
+                Attribute::AcpCreateAttr,
+                Attribute::OAuth2RsBasicSecret.into(),
+            ),
+        )?;
+        write_txn.internal_modify_uuid(
+            UUID_IDM_ACP_OAUTH2_MANAGE_V1,
+            &ModifyList::new_append(
+                Attribute::AcpModifyPresentAttr,
+                Attribute::OAuth2RsBasicSecret.into(),
+            ),
+        )?;
+
         // Execute whatever operations we have batched up and ready to go. This is needed
         // to preserve ordering of the operations - if we reloaded after a remigrate then
         // we would have skipped the patch level fix which needs to have occurred *first*.
-- 
2.51.0
+122 −0
Original line number Diff line number Diff line
From 29dab03201185675d116dd5da6928c6ca3ad30ff Mon Sep 17 00:00:00 2001
From: oddlama <oddlama@oddlama.org>
Date: Mon, 10 Nov 2025 20:01:07 +0100
Subject: [PATCH 2/2] recover account

---
 server/core/src/actors/internal.rs |  5 +++--
 server/core/src/admin.rs           |  6 +++---
 server/daemon/src/main.rs          | 23 ++++++++++++++++++++++-
 server/daemon/src/opt.rs           |  7 +++++++
 4 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/server/core/src/actors/internal.rs b/server/core/src/actors/internal.rs
index b3708f36d..6a52735fc 100644
--- a/server/core/src/actors/internal.rs
+++ b/server/core/src/actors/internal.rs
@@ -186,17 +186,18 @@ impl QueryServerWriteV1 {
 
     #[instrument(
         level = "info",
-        skip(self, eventid),
+        skip(self, password, eventid),
         fields(uuid = ?eventid)
     )]
     pub(crate) async fn handle_admin_recover_account(
         &self,
         name: String,
+        password: Option<String>,
         eventid: Uuid,
     ) -> Result<String, OperationError> {
         let ct = duration_from_epoch_now();
         let mut idms_prox_write = self.idms.proxy_write(ct).await?;
-        let pw = idms_prox_write.recover_account(name.as_str(), None)?;
+        let pw = idms_prox_write.recover_account(name.as_str(), password.as_deref())?;
 
         idms_prox_write.commit().map(|()| pw)
     }
diff --git a/server/core/src/admin.rs b/server/core/src/admin.rs
index b74cc90c5..660e3de8f 100644
--- a/server/core/src/admin.rs
+++ b/server/core/src/admin.rs
@@ -24,7 +24,7 @@ pub use kanidm_proto::internal::{
 
 #[derive(Serialize, Deserialize, Debug)]
 pub enum AdminTaskRequest {
-    RecoverAccount { name: String },
+    RecoverAccount { name: String, password: Option<String> },
     DisableAccount { name: String },
     ShowReplicationCertificate,
     RenewReplicationCertificate,
@@ -334,8 +334,8 @@ async fn handle_client(
 
         let resp = async {
             match req {
-                AdminTaskRequest::RecoverAccount { name } => {
-                    match server_rw.handle_admin_recover_account(name, eventid).await {
+                AdminTaskRequest::RecoverAccount { name, password } => {
+                    match server_rw.handle_admin_recover_account(name, password, eventid).await {
                         Ok(password) => AdminTaskResponse::RecoverAccount { password },
                         Err(e) => {
                             error!(err = ?e, "error during recover-account");
diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs
index 2ad7830cc..52fa8d2d9 100644
--- a/server/daemon/src/main.rs
+++ b/server/daemon/src/main.rs
@@ -832,13 +832,34 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
                 .await;
             }
         }
-        KanidmdOpt::RecoverAccount { name } => {
+        KanidmdOpt::RecoverAccount { name, from_environment } => {
             info!("Running account recovery ...");
             let output_mode: ConsoleOutputMode = opt.output_mode.into();
+            let password = if *from_environment {
+                match std::env::var("KANIDM_RECOVER_ACCOUNT_PASSWORD_FILE") {
+                    Ok(path) => match tokio::fs::read_to_string(&path).await {
+                        Ok(contents) => Some(contents),
+                        Err(e) => {
+                            error!("Failed to read password file '{}': {}", path, e);
+                            return ExitCode::FAILURE;
+                        }
+                    },
+                    Err(_) => match std::env::var("KANIDM_RECOVER_ACCOUNT_PASSWORD") {
+                        Ok(val) => Some(val),
+                        Err(_) => {
+                            error!("Neither KANIDM_RECOVER_ACCOUNT_PASSWORD_FILE nor KANIDM_RECOVER_ACCOUNT_PASSWORD was set");
+                            return ExitCode::FAILURE;
+                        }
+                    }
+                }
+            } else {
+                None
+            };
             submit_admin_req(
                 config.adminbindpath.as_str(),
                 AdminTaskRequest::RecoverAccount {
                     name: name.to_owned(),
+                    password,
                 },
                 output_mode,
             )
diff --git a/server/daemon/src/opt.rs b/server/daemon/src/opt.rs
index 05c5b9fb3..834b8f9cf 100644
--- a/server/daemon/src/opt.rs
+++ b/server/daemon/src/opt.rs
@@ -158,6 +158,13 @@ enum KanidmdOpt {
         #[clap(value_parser)]
         /// The account name to recover credentials for.
         name: String,
+        /// Use a password given via an environment variable.
+        /// - `KANIDM_RECOVER_ACCOUNT_PASSWORD_FILE` takes precedence and reads the desired
+        ///    password from the given file
+        /// - `KANIDM_RECOVER_ACCOUNT_PASSWORD` directly takes a
+        ///    password - beware that this will leave the password in the environment
+        #[clap(long = "from-environment")]
+        from_environment: bool,
     },
     #[clap(name = "disable-account")]
     /// Disable an account so that it can not be used. This can be reset with `recover-account`.
-- 
2.51.0
+6 −0
Original line number Diff line number Diff line
@@ -8307,14 +8307,20 @@ with pkgs;
      kanidm_1_8 = callPackage ../servers/kanidm/1_8.nix {
        kanidmWithSecretProvisioning = kanidmWithSecretProvisioning_1_8;
      };
      kanidm_1_9 = callPackage ../servers/kanidm/1_9.nix {
        kanidmWithSecretProvisioning = kanidmWithSecretProvisioning_1_8;
      };

      kanidmWithSecretProvisioning_1_7 = kanidm_1_7.override { enableSecretProvisioning = true; };
      kanidmWithSecretProvisioning_1_8 = kanidm_1_8.override { enableSecretProvisioning = true; };
      kanidmWithSecretProvisioning_1_9 = kanidm_1_9.override { enableSecretProvisioning = true; };
    })
    kanidm_1_7
    kanidm_1_8
    kanidm_1_9
    kanidmWithSecretProvisioning_1_7
    kanidmWithSecretProvisioning_1_8
    kanidmWithSecretProvisioning_1_9
    ;

  leafnode = callPackage ../servers/news/leafnode { };