all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-backup 4/6] api: access: domains: add get/create/update/delete domain call
Date: Fri,  9 Jul 2021 13:43:59 +0200	[thread overview]
Message-ID: <20210709114401.277841-5-d.csapak@proxmox.com> (raw)
In-Reply-To: <20210709114401.277841-1-d.csapak@proxmox.com>

modeled like our other section config api calls
two drawbacks of doing it this way:
* we have to copy some api properties again for the update call,
  since not all of them are updateable (username-claim)
* we only handle openid for now, which we would have to change
  when we add ldap/ad

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/access/domain.rs | 280 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 277 insertions(+), 3 deletions(-)

diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs
index c850603a..6b1a4743 100644
--- a/src/api2/access/domain.rs
+++ b/src/api2/access/domain.rs
@@ -1,6 +1,6 @@
 //! List Authentication domains/realms
 
-use anyhow::{Error};
+use anyhow::{bail, Error};
 
 use serde::{Deserialize, Serialize};
 use serde_json::{json, Value};
@@ -8,7 +8,11 @@ use serde_json::{json, Value};
 use proxmox::api::{api, Permission, Router, RpcEnvironment};
 
 use crate::api2::types::*;
-use crate::config::domains::{OpenIdRealmConfig, OpenIdUserAttribute};
+use crate::config::{
+    self,
+    acl::{PRIV_REALM_ALLOCATE, PRIV_SYS_AUDIT},
+    domains::{OpenIdRealmConfig, OpenIdUserAttribute},
+};
 
 #[api]
 #[derive(Deserialize, Serialize, PartialEq, Eq)]
@@ -158,5 +162,275 @@ fn list_domains(mut rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<BasicRealmInf
     Ok(list)
 }
 
+#[api(
+    input: {
+        properties: {
+            realm: {
+                schema: REALM_ID_SCHEMA,
+            },
+        },
+    },
+    returns: {
+        type: RealmInfo,
+    },
+    access: {
+        permission: &Permission::Privilege(&["access", "domains"], PRIV_SYS_AUDIT | PRIV_REALM_ALLOCATE, true),
+    },
+)]
+/// Get information about a realm
+fn get_domain(realm: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<RealmInfo, Error> {
+    let entry = match realm.as_str() {
+        "pam" => json!({
+            "realm": "pam",
+            "type": "pam",
+            "comment": "Linux PAM standard authentication",
+            "default": Some(true),
+        }),
+        "pbs" => json!({
+            "realm": "pbs",
+            "type": "pbs",
+            "comment": "Proxmox Backup authentication server",
+        }),
+        _ => {
+            let (config, digest) = config::domains::config()?;
+            rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
+            if let Some((section_type, v)) = config.sections.get(&realm) {
+                let mut entry = v.clone();
+                entry["type"] = Value::from(section_type.clone());
+                entry
+            } else {
+                bail!("domain '{}' does not exist", realm);
+            }
+        }
+    };
+
+    Ok(serde_json::from_value(entry)?)
+}
+
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            info: {
+                type: RealmInfo,
+                flatten: true,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false),
+    },
+)]
+/// Create a realm
+fn create_domain(param: Value) -> Result<(), Error> {
+    let basic_info: BasicRealmInfo = serde_json::from_value(param.clone())?;
+
+    // for now we only have to care about openid
+    if basic_info.ty != RealmType::OpenId {
+        bail!(
+            "Cannot create realm of type '{}'",
+            serde_json::to_string(&basic_info.ty)?
+        );
+    }
+
+    let new_realm: OpenIdRealmConfig = serde_json::from_value(param)?;
+    let _lock = config::domains::lock_config()?;
+
+    let (mut config, _digest) = config::domains::config()?;
+
+    let existing: Vec<OpenIdRealmConfig> = config.convert_to_typed_array("openid")?;
+
+    for realm in existing {
+        if realm.realm == new_realm.realm {
+            bail!("Entry '{}' already exists", realm.realm);
+        }
+    }
+
+    config.set_data(&new_realm.realm, "openid", &new_realm)?;
+
+    config::domains::save_config(&config)?;
+
+    Ok(())
+}
+
+#[api]
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+#[allow(non_camel_case_types)]
+pub enum DeletableProperty {
+    /// Delete the comment property.
+    comment,
+    /// Delete the client-key property.
+    client_key,
+    /// Delete the autocreate property.
+    autocreate,
+}
+
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            realm: {
+                schema: REALM_ID_SCHEMA,
+            },
+            comment: {
+                optional: true,
+                schema: SINGLE_LINE_COMMENT_SCHEMA,
+            },
+            "issuer-url": {
+                description: "OpenID Issuer Url",
+                type: String,
+                optional: true,
+            },
+            "client-id": {
+                description: "OpenID Client ID",
+                type: String,
+                optional: true,
+            },
+            "client-key": {
+                description: "OpenID Client Key",
+                type: String,
+                optional: true,
+            },
+            autocreate: {
+                description: "Automatically create users if they do not exist.",
+                optional: true,
+                type: bool,
+            },
+            delete: {
+                description: "List of properties to delete.",
+                type: Array,
+                optional: true,
+                items: {
+                    type: DeletableProperty,
+                }
+            },
+            digest: {
+                optional: true,
+                schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false),
+    },
+)]
+/// Update a realm
+fn update_domain(
+    realm: String,
+    comment: Option<String>,
+    issuer_url: Option<String>,
+    client_id: Option<String>,
+    client_key: Option<String>,
+    autocreate: Option<bool>,
+    delete: Option<Vec<DeletableProperty>>,
+    digest: Option<String>,
+    _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+    let _lock = config::domains::lock_config()?;
+
+    let (mut config, expected_digest) = config::domains::config()?;
+
+    if let Some(ref digest) = digest {
+        let digest = proxmox::tools::hex_to_digest(digest)?;
+        crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
+    }
+
+    // only have to worry about openid for now
+    let mut data: OpenIdRealmConfig = config.lookup("openid", realm.as_str())?;
+
+    if let Some(delete) = delete {
+        for delete_prop in delete {
+            match delete_prop {
+                DeletableProperty::comment => data.comment = None,
+                DeletableProperty::client_key => data.client_key = None,
+                DeletableProperty::autocreate => data.autocreate = None,
+            }
+        }
+    }
+
+    if let Some(comment) = comment {
+        let comment = comment.trim().to_string();
+        if comment.is_empty() {
+            data.comment = None;
+        } else {
+            data.comment = Some(comment);
+        }
+    }
+
+    if let Some(issuer_url) = issuer_url {
+        data.issuer_url = issuer_url
+    };
+    if let Some(client_id) = client_id {
+        data.client_id = client_id
+    };
+    if let Some(client_key) = client_key {
+        data.client_key = if client_key.is_empty() {
+            None
+        } else {
+            Some(client_key)
+        };
+    };
+    if let Some(autocreate) = autocreate {
+        data.autocreate = Some(autocreate)
+    };
+
+    config.set_data(&realm, "openid", &data)?;
+
+    config::domains::save_config(&config)?;
+
+    Ok(())
+}
+
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            realm: {
+                schema: REALM_ID_SCHEMA,
+            },
+            digest: {
+                optional: true,
+                schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false),
+    },
+)]
+/// Delete a realm
+fn delete_domain(realm: String, digest: Option<String>) -> Result<(), Error> {
+    if realm == "pam" || realm == "pbs" {
+        bail!("cannot remove realm '{}'", realm);
+    }
+    let _lock = config::domains::lock_config()?;
+
+    let (mut config, expected_digest) = config::domains::config()?;
+
+    if let Some(ref digest) = digest {
+        let digest = proxmox::tools::hex_to_digest(digest)?;
+        crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
+    }
+
+    match config.sections.get(&realm) {
+        Some(_) => {
+            config.sections.remove(&realm);
+        }
+        None => bail!("realm '{}' does not exist.", realm),
+    }
+
+    config::domains::save_config(&config)?;
+
+    Ok(())
+}
+
 pub const ROUTER: Router = Router::new()
-    .get(&API_METHOD_LIST_DOMAINS);
+    .get(&API_METHOD_LIST_DOMAINS)
+    .post(&API_METHOD_CREATE_DOMAIN)
+    .match_all("realm", &DOMAIN_ROUTER);
+
+const DOMAIN_ROUTER: Router = Router::new()
+    .get(&API_METHOD_GET_DOMAIN)
+    .put(&API_METHOD_UPDATE_DOMAIN)
+    .delete(&API_METHOD_DELETE_DOMAIN);
-- 
2.30.2





  parent reply	other threads:[~2021-07-09 11:44 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-07-09 11:43 [pbs-devel] [PATCH proxmox-backup 0/6] add realm api/ui Dominik Csapak
2021-07-09 11:43 ` [pbs-devel] [PATCH proxmox-backup 1/6] api: access: domains: add BasicRealmInfo struct and use it Dominik Csapak
2021-07-09 11:43 ` [pbs-devel] [PATCH proxmox-backup 2/6] config: acl: add PRIV_REALM_ALLOCATE Dominik Csapak
2021-07-09 11:43 ` [pbs-devel] [PATCH proxmox-backup 3/6] api: access: domains: add ExtraRealmInfo and RealmInfo structs Dominik Csapak
2021-07-09 11:43 ` Dominik Csapak [this message]
2021-07-09 11:44 ` [pbs-devel] [PATCH proxmox-backup 5/6] ui: add Authentication tab to Access Control Dominik Csapak
2021-07-09 11:44 ` [pbs-devel] [PATCH proxmox-backup 6/6] ui: add /access/domains to PermissionPathsStore Dominik Csapak
2021-07-12  4:11 ` [pbs-devel] applied-series: [PATCH proxmox-backup 0/6] add realm api/ui Thomas Lamprecht

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210709114401.277841-5-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal