* [pbs-devel] [PATH proxmox-backup v1 01/12] depend on openid-connect-rs
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 02/12] config: new domains.cfg to configure openid realm Dietmar Maurer
` (10 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
Cargo.toml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Cargo.toml b/Cargo.toml
index 976f18bc..7f165efb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,6 +84,8 @@ crossbeam-channel = "0.5"
proxmox-acme-rs = "0.2.1"
+proxmox-openid = "0.3.0"
+
[features]
default = []
#valgrind = ["valgrind_request"]
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 02/12] config: new domains.cfg to configure openid realm
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 01/12] depend on openid-connect-rs Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 03/12] check_acl_path: add /access/domains Dietmar Maurer
` (9 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
Or other realmy types...
---
src/api2/access/domain.rs | 21 +++++++
src/config.rs | 1 +
src/config/domains.rs | 122 ++++++++++++++++++++++++++++++++++++++
3 files changed, 144 insertions(+)
create mode 100644 src/config/domains.rs
diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs
index a2fd746d..2dff9d32 100644
--- a/src/api2/access/domain.rs
+++ b/src/api2/access/domain.rs
@@ -38,10 +38,31 @@ use crate::api2::types::*;
)]
/// Authentication domain/realm index.
fn list_domains() -> Result<Value, Error> {
+
let mut list = Vec::new();
+
list.push(json!({ "realm": "pam", "comment": "Linux PAM standard authentication", "default": true }));
list.push(json!({ "realm": "pbs", "comment": "Proxmox Backup authentication server" }));
+
+ let (config, _digest) = crate::config::domains::config()?;
+
+ for (realm, (section_type, v)) in config.sections.iter() {
+ let mut item = json!({
+ "type": section_type,
+ "realm": realm,
+ });
+
+ if v["comment"].as_str().is_some() {
+ item["comment"] = v["comment"].clone();
+ }
+ list.push(item);
+
+ }
+
Ok(list.into())
+
+
+
}
pub const ROUTER: Router = Router::new()
diff --git a/src/config.rs b/src/config.rs
index b9cd6281..329315ec 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -31,6 +31,7 @@ pub mod drive;
pub mod media_pool;
pub mod tape_encryption_keys;
pub mod tape_job;
+pub mod domains;
/// Check configuration directory permissions
///
diff --git a/src/config/domains.rs b/src/config/domains.rs
new file mode 100644
index 00000000..ee6fd754
--- /dev/null
+++ b/src/config/domains.rs
@@ -0,0 +1,122 @@
+use anyhow::{Error};
+use lazy_static::lazy_static;
+use std::collections::HashMap;
+use serde::{Serialize, Deserialize};
+
+use proxmox::api::{
+ api,
+ schema::*,
+ section_config::{
+ SectionConfig,
+ SectionConfigData,
+ SectionConfigPlugin,
+ }
+};
+
+use proxmox::tools::fs::{
+ open_file_locked,
+ replace_file,
+ CreateOptions,
+};
+
+use crate::api2::types::*;
+
+pub const OPENID_STATE_DIR: &str = "/var/run/proxmox-backup";
+
+lazy_static! {
+ pub static ref CONFIG: SectionConfig = init();
+}
+
+
+#[api(
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ "issuer-url": {
+ description: "OpenID Issuer Url",
+ type: String,
+ },
+ "client-id": {
+ description: "OpenID Client ID",
+ type: String,
+ },
+ "client-key": {
+ description: "OpenID Client Key",
+ type: String,
+ optional: true,
+ },
+ comment: {
+ optional: true,
+ schema: SINGLE_LINE_COMMENT_SCHEMA,
+ },
+ },
+)]
+#[derive(Serialize,Deserialize)]
+#[serde(rename_all="kebab-case")]
+/// OpenID configuration properties.
+pub struct OpenIdRealmConfig {
+ pub realm: String,
+ pub issuer_url: String,
+ pub client_id: String,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub client_key: Option<String>,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub comment: Option<String>,
+}
+
+fn init() -> SectionConfig {
+ let obj_schema = match OpenIdRealmConfig::API_SCHEMA {
+ Schema::Object(ref obj_schema) => obj_schema,
+ _ => unreachable!(),
+ };
+
+ let plugin = SectionConfigPlugin::new("openid".to_string(), Some(String::from("realm")), obj_schema);
+ let mut config = SectionConfig::new(&REALM_ID_SCHEMA);
+ config.register_plugin(plugin);
+
+ config
+}
+
+pub const DOMAINS_CFG_FILENAME: &str = "/etc/proxmox-backup/domains.cfg";
+pub const DOMAINS_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.domains.lck";
+
+/// Get exclusive lock
+pub fn lock_config() -> Result<std::fs::File, Error> {
+ open_file_locked(DOMAINS_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)
+}
+
+pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> {
+
+ let content = proxmox::tools::fs::file_read_optional_string(DOMAINS_CFG_FILENAME)?
+ .unwrap_or_else(|| "".to_string());
+
+ let digest = openssl::sha::sha256(content.as_bytes());
+ let data = CONFIG.parse(DOMAINS_CFG_FILENAME, &content)?;
+ Ok((data, digest))
+}
+
+pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
+ let raw = CONFIG.write(DOMAINS_CFG_FILENAME, &config)?;
+
+ let backup_user = crate::backup::backup_user()?;
+ let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
+ // set the correct owner/group/permissions while saving file
+ // owner(rw) = root, group(r)= backup
+ let options = CreateOptions::new()
+ .perm(mode)
+ .owner(nix::unistd::ROOT)
+ .group(backup_user.gid);
+
+ replace_file(DOMAINS_CFG_FILENAME, raw.as_bytes(), options)?;
+
+ Ok(())
+}
+
+// shell completion helper
+pub fn complete_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
+ match config() {
+ Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(),
+ Err(_) => return vec![],
+ }
+}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 03/12] check_acl_path: add /access/domains
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 01/12] depend on openid-connect-rs Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 02/12] config: new domains.cfg to configure openid realm Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 04/12] add API to manage openid realms Dietmar Maurer
` (8 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/config/acl.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/config/acl.rs b/src/config/acl.rs
index 04d42854..b04dfc0c 100644
--- a/src/config/acl.rs
+++ b/src/config/acl.rs
@@ -283,7 +283,7 @@ pub fn check_acl_path(path: &str) -> Result<(), Error> {
return Ok(());
}
match components[1] {
- "acl" | "users" => {
+ "acl" | "users" | "domains" => {
if components_len == 2 {
return Ok(());
}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 04/12] add API to manage openid realms
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (2 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 03/12] check_acl_path: add /access/domains Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 05/12] cli: add CLI " Dietmar Maurer
` (7 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/api2/config/access/mod.rs | 8 +-
src/api2/config/access/openid.rs | 264 +++++++++++++++++++++++++++++++
2 files changed, 271 insertions(+), 1 deletion(-)
create mode 100644 src/api2/config/access/openid.rs
diff --git a/src/api2/config/access/mod.rs b/src/api2/config/access/mod.rs
index 659815e0..6e7d98be 100644
--- a/src/api2/config/access/mod.rs
+++ b/src/api2/config/access/mod.rs
@@ -1,9 +1,15 @@
use proxmox::api::{Router, SubdirMap};
use proxmox::list_subdirs_api_method;
+use proxmox::{identity, sortable};
pub mod tfa;
+pub mod openid;
-const SUBDIRS: SubdirMap = &[("tfa", &tfa::ROUTER)];
+#[sortable]
+const SUBDIRS: SubdirMap = &sorted!([
+ ("openid", &openid::ROUTER),
+ ("tfa", &tfa::ROUTER),
+]);
pub const ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SUBDIRS))
diff --git a/src/api2/config/access/openid.rs b/src/api2/config/access/openid.rs
new file mode 100644
index 00000000..15fddaf0
--- /dev/null
+++ b/src/api2/config/access/openid.rs
@@ -0,0 +1,264 @@
+/// Configure OpenId realms
+
+use anyhow::{bail, Error};
+use serde_json::Value;
+use ::serde::{Deserialize, Serialize};
+
+use proxmox::api::{api, Permission, Router, RpcEnvironment};
+
+use crate::config::domains::{self, OpenIdRealmConfig};
+use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY};
+use crate::api2::types::*;
+
+#[api(
+ input: {
+ properties: {},
+ },
+ returns: {
+ description: "List of configured OpenId realms.",
+ type: Array,
+ items: { type: OpenIdRealmConfig },
+ },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// List configured OpenId realms
+pub fn list_openid_realms(
+ _param: Value,
+ mut rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<OpenIdRealmConfig>, Error> {
+
+ let (config, digest) = domains::config()?;
+
+ let list = config.convert_to_typed_array("openid")?;
+
+ rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
+
+ Ok(list)
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ config: {
+ type: OpenIdRealmConfig,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_PERMISSIONS_MODIFY, false),
+ },
+)]
+/// Create a new OpenId realm
+pub fn create_openid_realm(config: OpenIdRealmConfig) -> Result<(), Error> {
+
+ let _lock = domains::lock_config()?;
+
+ let (mut domains, _digest) = domains::config()?;
+
+ if config.realm == "pbs" ||
+ config.realm == "pam" ||
+ domains.sections.get(&config.realm).is_some()
+ {
+ bail!("realm '{}' already exists.", config.realm);
+ }
+
+ domains.set_data(&config.realm, "openid", &config)?;
+
+ domains::save_config(&domains)?;
+
+ 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_PERMISSIONS_MODIFY, false),
+ },
+)]
+/// Remove a OpenID realm configuration
+pub fn delete_openid_realm(
+ realm: String,
+ digest: Option<String>,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+
+ let _lock = domains::lock_config()?;
+
+ let (mut domains, expected_digest) = 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)?;
+ }
+
+ if domains.sections.remove(&realm).is_none() {
+ bail!("realm '{}' does not exist.", realm);
+ }
+
+ domains::save_config(&domains)?;
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ },
+ },
+ returns: { type: OpenIdRealmConfig },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Read the OpenID realm configuration
+pub fn read_openid_realm(
+ realm: String,
+ mut rpcenv: &mut dyn RpcEnvironment,
+) -> Result<OpenIdRealmConfig, Error> {
+
+ let (domains, digest) = domains::config()?;
+
+ let config = domains.lookup("openid", &realm)?;
+
+ rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
+
+ Ok(config)
+}
+
+#[api()]
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all="kebab-case")]
+#[allow(non_camel_case_types)]
+/// Deletable property name
+pub enum DeletableProperty {
+ /// Delete the client key.
+ client_key,
+ /// Delete the comment property.
+ comment,
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_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,
+ },
+ comment: {
+ schema: SINGLE_LINE_COMMENT_SCHEMA,
+ optional: true,
+ },
+ delete: {
+ description: "List of properties to delete.",
+ type: Array,
+ optional: true,
+ items: {
+ type: DeletableProperty,
+ }
+ },
+ digest: {
+ optional: true,
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ },
+ },
+ },
+ returns: { type: OpenIdRealmConfig },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_PERMISSIONS_MODIFY, false),
+ },
+)]
+/// Update an OpenID realm configuration
+pub fn update_openid_realm(
+ realm: String,
+ issuer_url: Option<String>,
+ client_id: Option<String>,
+ client_key: Option<String>,
+ comment: Option<String>,
+ delete: Option<Vec<DeletableProperty>>,
+ digest: Option<String>,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+
+ let _lock = domains::lock_config()?;
+
+ let (mut domains, expected_digest) = 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)?;
+ }
+
+ let mut config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
+
+ if let Some(delete) = delete {
+ for delete_prop in delete {
+ match delete_prop {
+ DeletableProperty::client_key => { config.client_key = None; },
+ DeletableProperty::comment => { config.comment = None; },
+ }
+ }
+ }
+
+ if let Some(comment) = comment {
+ let comment = comment.trim().to_string();
+ if comment.is_empty() {
+ config.comment = None;
+ } else {
+ config.comment = Some(comment);
+ }
+ }
+
+ if let Some(issuer_url) = issuer_url { config.issuer_url = issuer_url; }
+ if let Some(client_id) = client_id { config.client_id = client_id; }
+
+ if client_key.is_some() { config.client_key = client_key; }
+
+ domains.set_data(&realm, "openid", &config)?;
+
+ domains::save_config(&domains)?;
+
+ Ok(())
+}
+
+const ITEM_ROUTER: Router = Router::new()
+ .get(&API_METHOD_READ_OPENID_REALM)
+ .put(&API_METHOD_UPDATE_OPENID_REALM)
+ .delete(&API_METHOD_DELETE_OPENID_REALM);
+
+pub const ROUTER: Router = Router::new()
+ .get(&API_METHOD_LIST_OPENID_REALMS)
+ .post(&API_METHOD_CREATE_OPENID_REALM)
+ .match_all("id", &ITEM_ROUTER);
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 05/12] cli: add CLI to manage openid realms.
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (3 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 04/12] add API to manage openid realms Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 06/12] api: add openid redirect API Dietmar Maurer
` (6 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/bin/proxmox-backup-manager.rs | 1 +
src/bin/proxmox_backup_manager/mod.rs | 2 +
src/bin/proxmox_backup_manager/openid.rs | 99 ++++++++++++++++++++++++
src/config/domains.rs | 9 +++
4 files changed, 111 insertions(+)
create mode 100644 src/bin/proxmox_backup_manager/openid.rs
diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs
index c3806a31..461d45bd 100644
--- a/src/bin/proxmox-backup-manager.rs
+++ b/src/bin/proxmox-backup-manager.rs
@@ -354,6 +354,7 @@ fn main() {
.insert("network", network_commands())
.insert("node", node_commands())
.insert("user", user_commands())
+ .insert("openid", openid_commands())
.insert("remote", remote_commands())
.insert("garbage-collection", garbage_collection_commands())
.insert("acme", acme_mgmt_cli())
diff --git a/src/bin/proxmox_backup_manager/mod.rs b/src/bin/proxmox_backup_manager/mod.rs
index 21004bbe..a3a16246 100644
--- a/src/bin/proxmox_backup_manager/mod.rs
+++ b/src/bin/proxmox_backup_manager/mod.rs
@@ -24,3 +24,5 @@ mod disk;
pub use disk::*;
mod node;
pub use node::*;
+mod openid;
+pub use openid::*;
diff --git a/src/bin/proxmox_backup_manager/openid.rs b/src/bin/proxmox_backup_manager/openid.rs
new file mode 100644
index 00000000..13915339
--- /dev/null
+++ b/src/bin/proxmox_backup_manager/openid.rs
@@ -0,0 +1,99 @@
+use anyhow::Error;
+use serde_json::Value;
+
+use proxmox::api::{api, cli::*, RpcEnvironment, ApiHandler};
+
+use proxmox_backup::{config, api2, api2::types::REALM_ID_SCHEMA};
+
+
+#[api(
+ input: {
+ properties: {
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+/// List configured OpenId realms
+fn list_openid_realms(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+ let output_format = get_output_format(¶m);
+
+ let info = &api2::config::access::openid::API_METHOD_LIST_OPENID_REALMS;
+ let mut data = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options()
+ .column(ColumnConfig::new("realm"))
+ .column(ColumnConfig::new("issuer-url"))
+ .column(ColumnConfig::new("comment"));
+
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(Value::Null)
+}
+#[api(
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+
+/// Show OpenID realm configuration
+fn show_openid_realm(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+ let output_format = get_output_format(¶m);
+
+ let info = &api2::config::access::openid::API_METHOD_READ_OPENID_REALM;
+ let mut data = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options();
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(Value::Null)
+}
+
+pub fn openid_commands() -> CommandLineInterface {
+
+ let cmd_def = CliCommandMap::new()
+ .insert("list", CliCommand::new(&&API_METHOD_LIST_OPENID_REALMS))
+ .insert("show", CliCommand::new(&&API_METHOD_SHOW_OPENID_REALM)
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ .insert("create",
+ CliCommand::new(&api2::config::access::openid::API_METHOD_CREATE_OPENID_REALM)
+ .arg_param(&["realm"])
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ .insert("update",
+ CliCommand::new(&api2::config::access::openid::API_METHOD_UPDATE_OPENID_REALM)
+ .arg_param(&["realm"])
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ .insert("delete",
+ CliCommand::new(&api2::config::access::openid::API_METHOD_DELETE_OPENID_REALM)
+ .arg_param(&["realm"])
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ ;
+
+ cmd_def.into()
+}
diff --git a/src/config/domains.rs b/src/config/domains.rs
index ee6fd754..ce3f6f23 100644
--- a/src/config/domains.rs
+++ b/src/config/domains.rs
@@ -120,3 +120,12 @@ pub fn complete_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<
Err(_) => return vec![],
}
}
+
+pub fn complete_openid_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
+ match config() {
+ Ok((data, _digest)) => data.sections.iter()
+ .filter_map(|(id, (t, _))| if t == "openid" { Some(id.to_string()) } else { None })
+ .collect(),
+ Err(_) => return vec![],
+ }
+}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 06/12] api: add openid redirect API
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (4 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 05/12] cli: add CLI " Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 07/12] implement new helper is_active_user_id() Dietmar Maurer
` (5 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/api2/access/domain.rs | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs
index 2dff9d32..3c9e3615 100644
--- a/src/api2/access/domain.rs
+++ b/src/api2/access/domain.rs
@@ -8,6 +8,7 @@ use proxmox::api::{api, Permission};
use proxmox::api::router::Router;
use crate::api2::types::*;
+use crate::config::domains::{OPENID_STATE_DIR, OpenIdRealmConfig};
#[api(
returns: {
@@ -60,10 +61,48 @@ fn list_domains() -> Result<Value, Error> {
}
Ok(list.into())
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ "redirect-url": {
+ description: "Redirection Url. The client should set this to used server url.",
+ type: String,
+ },
+ },
+ },
+ returns: {
+ description: "Redirection URL.",
+ type: String,
+ },
+ access: {
+ description: "Anyone can access this (before the user is authenticated).",
+ permission: &Permission::World,
+ },
+)]
+/// Create OpenID Redirect Session
+fn create_redirect_session(
+ realm: String,
+ redirect_url: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Value, Error> {
+
+ let (domains, _digest) = crate::config::domains::config()?;
+ let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
+ let open_id = config.authenticator(&redirect_url)?;
+ let url = open_id.authorize_url(OPENID_STATE_DIR, &realm)?
+ .to_string();
+ Ok(url.into())
}
pub const ROUTER: Router = Router::new()
+ .post(&API_METHOD_CREATE_REDIRECT_SESSION)
.get(&API_METHOD_LIST_DOMAINS);
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 07/12] implement new helper is_active_user_id()
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (5 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 06/12] api: add openid redirect API Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 08/12] api: add openid-login endpoint Dietmar Maurer
` (4 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/config/cached_user_info.rs | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/config/cached_user_info.rs b/src/config/cached_user_info.rs
index c85d643c..a574043f 100644
--- a/src/config/cached_user_info.rs
+++ b/src/config/cached_user_info.rs
@@ -65,10 +65,8 @@ impl CachedUserInfo {
}
}
- /// Test if a authentication id is enabled and not expired
- pub fn is_active_auth_id(&self, auth_id: &Authid) -> bool {
- let userid = auth_id.user();
-
+ /// Test if a user_id is enabled and not expired
+ pub fn is_active_user_id(&self, userid: &Userid) -> bool {
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
if !info.enable.unwrap_or(true) {
return false;
@@ -78,7 +76,17 @@ impl CachedUserInfo {
return false;
}
}
+ true
} else {
+ false
+ }
+ }
+
+ /// Test if a authentication id is enabled and not expired
+ pub fn is_active_auth_id(&self, auth_id: &Authid) -> bool {
+ let userid = auth_id.user();
+
+ if !self.is_active_user_id(userid) {
return false;
}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 08/12] api: add openid-login endpoint
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (6 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 07/12] implement new helper is_active_user_id() Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 09/12] ui: implement OpenId login Dietmar Maurer
` (3 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/api2/access.rs | 91 +++++++++++++++++++++++++++++++++++++++
src/api2/access/domain.rs | 2 +-
src/config/domains.rs | 14 ++++++
3 files changed, 106 insertions(+), 1 deletion(-)
diff --git a/src/api2/access.rs b/src/api2/access.rs
index 46725c97..5abb5cb4 100644
--- a/src/api2/access.rs
+++ b/src/api2/access.rs
@@ -5,16 +5,20 @@ use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
use std::collections::HashMap;
use std::collections::HashSet;
+use std::convert::TryFrom;
use proxmox::api::router::{Router, SubdirMap};
use proxmox::api::{api, Permission, RpcEnvironment};
use proxmox::{http_err, list_subdirs_api_method};
use proxmox::{identity, sortable};
+use proxmox_openid::OpenIdAuthenticator;
+
use crate::api2::types::*;
use crate::auth_helpers::*;
use crate::server::ticket::ApiTicket;
use crate::tools::ticket::{self, Empty, Ticket};
+use crate::config::domains::OpenIdRealmConfig;
use crate::config::acl as acl_config;
use crate::config::acl::{PRIVILEGES, PRIV_PERMISSIONS_MODIFY, PRIV_SYS_AUDIT};
@@ -235,6 +239,92 @@ pub fn create_ticket(
}
}
+#[api(
+ input: {
+ properties: {
+ state: {
+ description: "OpenId state.",
+ type: String,
+ },
+ code: {
+ description: "OpenId authorization code.",
+ type: String,
+ },
+ "redirect-url": {
+ description: "Redirection Url. The client should set this to used server url.",
+ type: String,
+ },
+ },
+ },
+ returns: {
+ properties: {
+ username: {
+ type: String,
+ description: "User name.",
+ },
+ ticket: {
+ type: String,
+ description: "Auth ticket.",
+ },
+ CSRFPreventionToken: {
+ type: String,
+ description: "Cross Site Request Forgery Prevention Token.",
+ },
+ },
+ },
+ protected: true,
+ access: {
+ permission: &Permission::World,
+ },
+)]
+/// Verify OpenID authorization code and create a ticket
+///
+/// Returns: An authentication ticket with additional infos.
+pub fn openid_login(
+ state: String,
+ code: String,
+ redirect_url: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Value, Error> {
+ let user_info = CachedUserInfo::new()?;
+
+ let backup_user = crate::backup::backup_user()?;
+
+ let (realm, private_auth_state) =
+ OpenIdAuthenticator::verify_public_auth_state(&state, backup_user.uid)?;
+
+ let (domains, _digest) = crate::config::domains::config()?;
+ let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
+
+ let open_id = config.authenticator(&redirect_url)?;
+
+ let info = open_id.verify_authorization_code(&code, &private_auth_state)?;
+
+ // eprintln!("VERIFIED {} {:?} {:?}", info.subject().as_str(), info.name(), info.email());
+
+ // fixme: allow to use other attributes
+ let unique_name = info.subject().as_str();
+
+ let user_id = Userid::try_from(format!("{}@{}", unique_name, realm))?;
+
+ if !user_info.is_active_user_id(&user_id) {
+ bail!("user account '{}' disabled or expired.", user_id);
+ }
+
+ let api_ticket = ApiTicket::full(user_id.clone());
+ let ticket = Ticket::new("PBS", &api_ticket)?.sign(private_auth_key(), None)?;
+ let token = assemble_csrf_prevention_token(csrf_secret(), &user_id);
+
+ crate::server::rest::auth_logger()?
+ .log(format!("successful auth for user '{}'", user_id));
+
+ Ok(json!({
+ "username": user_id,
+ "ticket": ticket,
+ "CSRFPreventionToken": token,
+ }))
+}
+
#[api(
protected: true,
input: {
@@ -423,6 +513,7 @@ const SUBDIRS: SubdirMap = &sorted!([
&Router::new().get(&API_METHOD_LIST_PERMISSIONS)
),
("ticket", &Router::new().post(&API_METHOD_CREATE_TICKET)),
+ ("openid-login", &Router::new().post(&API_METHOD_OPENID_LOGIN)),
("domains", &domain::ROUTER),
("roles", &role::ROUTER),
("users", &user::ROUTER),
diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs
index 3c9e3615..b325ae63 100644
--- a/src/api2/access/domain.rs
+++ b/src/api2/access/domain.rs
@@ -4,7 +4,7 @@ use anyhow::{Error};
use serde_json::{json, Value};
-use proxmox::api::{api, Permission};
+use proxmox::api::{api, Permission, RpcEnvironment};
use proxmox::api::router::Router;
use crate::api2::types::*;
diff --git a/src/config/domains.rs b/src/config/domains.rs
index ce3f6f23..007cf357 100644
--- a/src/config/domains.rs
+++ b/src/config/domains.rs
@@ -3,6 +3,8 @@ use lazy_static::lazy_static;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
+use proxmox_openid::{OpenIdAuthenticator, OpenIdConfig};
+
use proxmox::api::{
api,
schema::*,
@@ -65,6 +67,18 @@ pub struct OpenIdRealmConfig {
pub comment: Option<String>,
}
+impl OpenIdRealmConfig {
+
+ pub fn authenticator(&self, redirect_url: &str) -> Result<OpenIdAuthenticator, Error> {
+ let config = OpenIdConfig {
+ issuer_url: self.issuer_url.clone(),
+ client_id: self.client_id.clone(),
+ client_key: self.client_key.clone(),
+ };
+ OpenIdAuthenticator::discover(&config, redirect_url)
+ }
+}
+
fn init() -> SectionConfig {
let obj_schema = match OpenIdRealmConfig::API_SCHEMA {
Schema::Object(ref obj_schema) => obj_schema,
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 09/12] ui: implement OpenId login
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (7 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 08/12] api: add openid-login endpoint Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 10/12] cleanup user/token is_active() check Dietmar Maurer
` (2 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/api2/access.rs | 6 +--
www/Application.js | 8 +++-
www/LoginView.js | 100 ++++++++++++++++++++++++++++++++++++++++++++-
www/Utils.js | 8 ++++
4 files changed, 116 insertions(+), 6 deletions(-)
diff --git a/src/api2/access.rs b/src/api2/access.rs
index 5abb5cb4..e95db88b 100644
--- a/src/api2/access.rs
+++ b/src/api2/access.rs
@@ -18,7 +18,7 @@ use crate::api2::types::*;
use crate::auth_helpers::*;
use crate::server::ticket::ApiTicket;
use crate::tools::ticket::{self, Empty, Ticket};
-use crate::config::domains::OpenIdRealmConfig;
+use crate::config::domains::{OPENID_STATE_DIR, OpenIdRealmConfig};)
use crate::config::acl as acl_config;
use crate::config::acl::{PRIVILEGES, PRIV_PERMISSIONS_MODIFY, PRIV_SYS_AUDIT};
@@ -288,10 +288,8 @@ pub fn openid_login(
) -> Result<Value, Error> {
let user_info = CachedUserInfo::new()?;
- let backup_user = crate::backup::backup_user()?;
-
let (realm, private_auth_state) =
- OpenIdAuthenticator::verify_public_auth_state(&state, backup_user.uid)?;
+ OpenIdAuthenticator::verify_public_auth_state(OPENID_STATE_DIR, &state)?;
let (domains, _digest) = crate::config::domains::config()?;
let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
diff --git a/www/Application.js b/www/Application.js
index c4df600e..20ae26bd 100644
--- a/www/Application.js
+++ b/www/Application.js
@@ -49,9 +49,15 @@ Ext.define('PBS.Application', {
var provider = new Ext.state.LocalStorageProvider({ prefix: 'ext-pbs-' });
Ext.state.Manager.setProvider(provider);
+ let openid_login = false;
+ let param = PBS.Utils.openid_login_param();
+ if (param !== undefined) {
+ openid_login = true;
+ }
+
// show login window if not loggedin
var loggedin = Proxmox.Utils.authOK();
- if (!loggedin) {
+ if (openid_login || !loggedin) {
me.changeView('loginview', true);
} else {
me.changeView('mainview', true);
diff --git a/www/LoginView.js b/www/LoginView.js
index 6dd09646..f2fc5370 100644
--- a/www/LoginView.js
+++ b/www/LoginView.js
@@ -2,6 +2,21 @@ Ext.define('PBS.LoginView', {
extend: 'Ext.container.Container',
xtype: 'loginview',
+ viewModel: {
+ data: {
+ openid: false,
+ },
+ formulas: {
+ button_text: function(get) {
+ if (get("openid") === true) {
+ return gettext("Login (OpenID redirect)");
+ } else {
+ return gettext("Login");
+ }
+ },
+ },
+ },
+
controller: {
xclass: 'Ext.app.ViewController',
@@ -15,8 +30,33 @@ Ext.define('PBS.LoginView', {
return;
}
+ let redirect_url = location.origin;
+
let params = loginForm.getValues();
+ if (this.getViewModel().data.openid === true) {
+ let realm = params.realm;
+ try {
+ let resp = await PBS.Async.api2({
+ url: '/api2/extjs/access/domains',
+ params: {
+ realm: realm,
+ "redirect-url": redirect_url,
+ },
+ method: 'POST',
+ });
+ window.location = resp.result.data;
+ } catch (error) {
+ Proxmox.Utils.authClear();
+ loginForm.unmask();
+ Ext.MessageBox.alert(
+ gettext('Error'),
+ gettext('OpenId redirect failed. Please try again<br>Error: ' + error),
+ );
+ }
+ return;
+ }
+
params.username = params.username + '@' + params.realm;
delete params.realm;
@@ -98,6 +138,14 @@ Ext.define('PBS.LoginView', {
window.location.reload();
},
},
+ 'field[name=realm]': {
+ change: function(f, value) {
+ let record = f.store.getById(value);
+ if (record === undefined) return;
+ let data = record.data;
+ this.getViewModel().set("openid", data.type === "openid");
+ },
+ },
'button[reference=loginButton]': {
click: 'submitForm',
},
@@ -116,6 +164,43 @@ Ext.define('PBS.LoginView', {
var pwField = this.lookupReference('passwordField');
pwField.focus();
}
+
+ let param = PBS.Utils.openid_login_param();
+ if (param !== undefined) {
+ Proxmox.Utils.authClear();
+
+ let loginForm = this.lookupReference('loginForm');
+ loginForm.mask(gettext('OpenID login - please wait...'), 'x-mask-loading');
+
+ let redirect_url = location.origin;
+
+ Proxmox.Utils.API2Request({
+ url: '/api2/extjs/access/openid-login',
+ params: {
+ state: param.state,
+ code: param.code,
+ "redirect-url": redirect_url,
+ },
+ method: 'POST',
+ failure: function(response) {
+ loginForm.unmask();
+ Ext.MessageBox.alert(
+ gettext('Error'),
+ gettext('Login failed. Please try again<br>Error: ' + response.htmlStatus),
+ function() {
+ window.location = redirect_url;
+ },
+ );
+ },
+ success: function(response, options) {
+ loginForm.unmask();
+ let data = response.result.data;
+ PBS.Utils.updateLoginData(data);
+ PBS.app.changeView('mainview');
+ history.replaceState(null, '', redirect_url + '#pbsDashboard');
+ },
+ });
+ }
},
},
},
@@ -191,6 +276,10 @@ Ext.define('PBS.LoginView', {
itemId: 'usernameField',
reference: 'usernameField',
stateId: 'login-username',
+ bind: {
+ visible: "{!openid}",
+ disabled: "{openid}",
+ },
},
{
xtype: 'textfield',
@@ -199,6 +288,10 @@ Ext.define('PBS.LoginView', {
name: 'password',
itemId: 'passwordField',
reference: 'passwordField',
+ bind: {
+ visible: "{!openid}",
+ disabled: "{openid}",
+ },
},
{
xtype: 'pmxRealmComboBox',
@@ -223,9 +316,14 @@ Ext.define('PBS.LoginView', {
labelWidth: 250,
labelAlign: 'right',
submitValue: false,
+ bind: {
+ visible: "{!openid}",
+ },
},
{
- text: gettext('Login'),
+ bind: {
+ text: "{button_text}",
+ },
reference: 'loginButton',
formBind: true,
},
diff --git a/www/Utils.js b/www/Utils.js
index 6b378355..677f2204 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -326,6 +326,14 @@ Ext.define('PBS.Utils', {
};
},
+ openid_login_param: function() {
+ let param = Ext.Object.fromQueryString(window.location.search);
+ if (param.state !== undefined && param.code !== undefined) {
+ return param;
+ }
+ return undefined;
+ },
+
calculate_dedup_factor: function(gcstatus) {
let dedup = 1.0;
if (gcstatus['disk-bytes'] > 0) {
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 10/12] cleanup user/token is_active() check
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (8 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 09/12] ui: implement OpenId login Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 11/12] add openid autocreate account feature Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 12/12] implement openid user-attr configuration Dietmar Maurer
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/config/cached_user_info.rs | 25 ++++---------------------
src/config/user.rs | 32 ++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/src/config/cached_user_info.rs b/src/config/cached_user_info.rs
index a574043f..6cb64162 100644
--- a/src/config/cached_user_info.rs
+++ b/src/config/cached_user_info.rs
@@ -7,6 +7,7 @@ use anyhow::{Error, bail};
use proxmox::api::section_config::SectionConfigData;
use lazy_static::lazy_static;
use proxmox::api::UserInformation;
+use proxmox::tools::time::epoch_i64;
use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
use super::user::{ApiToken, User};
@@ -18,8 +19,6 @@ pub struct CachedUserInfo {
acl_tree: Arc<AclTree>,
}
-fn now() -> i64 { unsafe { libc::time(std::ptr::null_mut()) } }
-
struct ConfigCache {
data: Option<Arc<CachedUserInfo>>,
last_update: i64,
@@ -35,7 +34,7 @@ impl CachedUserInfo {
/// Returns a cached instance (up to 5 seconds old).
pub fn new() -> Result<Arc<Self>, Error> {
- let now = now();
+ let now = epoch_i64();
{ // limit scope
let cache = CACHED_CONFIG.read().unwrap();
if (now - cache.last_update) < 5 {
@@ -68,15 +67,7 @@ impl CachedUserInfo {
/// Test if a user_id is enabled and not expired
pub fn is_active_user_id(&self, userid: &Userid) -> bool {
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
- if !info.enable.unwrap_or(true) {
- return false;
- }
- if let Some(expire) = info.expire {
- if expire > 0 && expire <= now() {
- return false;
- }
- }
- true
+ info.is_active()
} else {
false
}
@@ -92,15 +83,7 @@ impl CachedUserInfo {
if auth_id.is_token() {
if let Ok(info) = self.user_cfg.lookup::<ApiToken>("token", &auth_id.to_string()) {
- if !info.enable.unwrap_or(true) {
- return false;
- }
- if let Some(expire) = info.expire {
- if expire > 0 && expire <= now() {
- return false;
- }
- }
- return true;
+ return info.is_active();
} else {
return false;
}
diff --git a/src/config/user.rs b/src/config/user.rs
index ff7e54e4..28e81876 100644
--- a/src/config/user.rs
+++ b/src/config/user.rs
@@ -83,6 +83,22 @@ pub struct ApiToken {
pub expire: Option<i64>,
}
+impl ApiToken {
+
+ pub fn is_active(&self) -> bool {
+ if !self.enable.unwrap_or(true) {
+ return false;
+ }
+ if let Some(expire) = self.expire {
+ let now = proxmox::tools::time::epoch_i64();
+ if expire > 0 && expire <= now {
+ return false;
+ }
+ }
+ true
+ }
+}
+
#[api(
properties: {
userid: {
@@ -132,6 +148,22 @@ pub struct User {
pub email: Option<String>,
}
+impl User {
+
+ pub fn is_active(&self) -> bool {
+ if !self.enable.unwrap_or(true) {
+ return false;
+ }
+ if let Some(expire) = self.expire {
+ let now = proxmox::tools::time::epoch_i64();
+ if expire > 0 && expire <= now {
+ return false;
+ }
+ }
+ true
+ }
+}
+
fn init() -> SectionConfig {
let mut config = SectionConfig::new(&Authid::API_SCHEMA);
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 11/12] add openid autocreate account feature
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (9 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 10/12] cleanup user/token is_active() check Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 12/12] implement openid user-attr configuration Dietmar Maurer
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/api2/access.rs | 25 ++++++++++++++++++++++++-
src/api2/config/access/openid.rs | 10 ++++++++++
src/config/domains.rs | 8 ++++++++
3 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/src/api2/access.rs b/src/api2/access.rs
index e95db88b..115779f3 100644
--- a/src/api2/access.rs
+++ b/src/api2/access.rs
@@ -11,6 +11,7 @@ use proxmox::api::router::{Router, SubdirMap};
use proxmox::api::{api, Permission, RpcEnvironment};
use proxmox::{http_err, list_subdirs_api_method};
use proxmox::{identity, sortable};
+use proxmox::tools::fs::open_file_locked;
use proxmox_openid::OpenIdAuthenticator;
@@ -306,7 +307,29 @@ pub fn openid_login(
let user_id = Userid::try_from(format!("{}@{}", unique_name, realm))?;
if !user_info.is_active_user_id(&user_id) {
- bail!("user account '{}' disabled or expired.", user_id);
+ if config.autocreate.unwrap_or(false) {
+ use crate::config::user;
+ let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
+ let user = user::User {
+ userid: user_id.clone(),
+ comment: None,
+ enable: None,
+ expire: None,
+ firstname: info.given_name().and_then(|n| n.get(None)).map(|n| n.to_string()),
+ lastname: info.family_name().and_then(|n| n.get(None)).map(|n| n.to_string()),
+ email: info.email().map(|e| e.to_string()),
+ };
+ let (mut config, _digest) = user::config()?;
+ if config.sections.get(user.userid.as_str()).is_some() {
+ bail!("autocreate user failed - '{}' already exists.", user.userid);
+ }
+ config.set_data(user.userid.as_str(), "user", &user)?;
+ user::save_config(&config)?;
+ // fixme: replace sleep with shared memory change notification
+ std::thread::sleep(std::time::Duration::new(6, 0));
+ } else {
+ bail!("user account '{}' missing, disabled or expired.", user_id);
+ }
}
let api_ticket = ApiTicket::full(user_id.clone());
diff --git a/src/api2/config/access/openid.rs b/src/api2/config/access/openid.rs
index 15fddaf0..9325de94 100644
--- a/src/api2/config/access/openid.rs
+++ b/src/api2/config/access/openid.rs
@@ -153,6 +153,8 @@ pub enum DeletableProperty {
client_key,
/// Delete the comment property.
comment,
+ /// Delete the autocreate property
+ autocreate,
}
#[api(
@@ -177,6 +179,11 @@ pub enum DeletableProperty {
type: String,
optional: true,
},
+ autocreate: {
+ description: "Automatically create users if they do not exist.",
+ optional: true,
+ type: bool,
+ },
comment: {
schema: SINGLE_LINE_COMMENT_SCHEMA,
optional: true,
@@ -206,6 +213,7 @@ pub fn update_openid_realm(
issuer_url: Option<String>,
client_id: Option<String>,
client_key: Option<String>,
+ autocreate: Option<bool>,
comment: Option<String>,
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
@@ -228,6 +236,7 @@ pub fn update_openid_realm(
match delete_prop {
DeletableProperty::client_key => { config.client_key = None; },
DeletableProperty::comment => { config.comment = None; },
+ DeletableProperty::autocreate => { config.autocreate = None; },
}
}
}
@@ -245,6 +254,7 @@ pub fn update_openid_realm(
if let Some(client_id) = client_id { config.client_id = client_id; }
if client_key.is_some() { config.client_key = client_key; }
+ if autocreate.is_some() { config.autocreate = autocreate; }
domains.set_data(&realm, "openid", &config)?;
diff --git a/src/config/domains.rs b/src/config/domains.rs
index 007cf357..7db1f0be 100644
--- a/src/config/domains.rs
+++ b/src/config/domains.rs
@@ -52,6 +52,12 @@ lazy_static! {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
+ autocreate: {
+ description: "Automatically create users if they do not exist.",
+ optional: true,
+ type: bool,
+ default: false,
+ },
},
)]
#[derive(Serialize,Deserialize)]
@@ -65,6 +71,8 @@ pub struct OpenIdRealmConfig {
pub client_key: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub comment: Option<String>,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub autocreate: Option<bool>,
}
impl OpenIdRealmConfig {
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATH proxmox-backup v1 12/12] implement openid user-attr configuration
2021-06-22 8:56 [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms Dietmar Maurer
` (10 preceding siblings ...)
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 11/12] add openid autocreate account feature Dietmar Maurer
@ 2021-06-22 8:56 ` Dietmar Maurer
11 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
---
src/api2/access.rs | 25 +++++++++++++++++++------
src/config/domains.rs | 22 ++++++++++++++++++++++
2 files changed, 41 insertions(+), 6 deletions(-)
diff --git a/src/api2/access.rs b/src/api2/access.rs
index 115779f3..6872b0db 100644
--- a/src/api2/access.rs
+++ b/src/api2/access.rs
@@ -19,7 +19,7 @@ use crate::api2::types::*;
use crate::auth_helpers::*;
use crate::server::ticket::ApiTicket;
use crate::tools::ticket::{self, Empty, Ticket};
-use crate::config::domains::{OPENID_STATE_DIR, OpenIdRealmConfig};)
+use crate::config::domains::{OPENID_STATE_DIR, OpenIdUserAttribute, OpenIdRealmConfig};
use crate::config::acl as acl_config;
use crate::config::acl::{PRIVILEGES, PRIV_PERMISSIONS_MODIFY, PRIV_SYS_AUDIT};
@@ -294,15 +294,28 @@ pub fn openid_login(
let (domains, _digest) = crate::config::domains::config()?;
let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
-
+
let open_id = config.authenticator(&redirect_url)?;
let info = open_id.verify_authorization_code(&code, &private_auth_state)?;
// eprintln!("VERIFIED {} {:?} {:?}", info.subject().as_str(), info.name(), info.email());
- // fixme: allow to use other attributes
- let unique_name = info.subject().as_str();
+ let unique_name = match config.user_attr {
+ None | Some(OpenIdUserAttribute::Subject) => info.subject().as_str(),
+ Some(OpenIdUserAttribute::Username) => {
+ match info.preferred_username() {
+ Some(name) => name.as_str(),
+ None => bail!("missing claim 'preferred_name'"),
+ }
+ }
+ Some(OpenIdUserAttribute::Email) => {
+ match info.email() {
+ Some(name) => name.as_str(),
+ None => bail!("missing claim 'email'"),
+ }
+ }
+ };
let user_id = Userid::try_from(format!("{}@{}", unique_name, realm))?;
@@ -338,7 +351,7 @@ pub fn openid_login(
crate::server::rest::auth_logger()?
.log(format!("successful auth for user '{}'", user_id));
-
+
Ok(json!({
"username": user_id,
"ticket": ticket,
@@ -446,7 +459,7 @@ pub fn list_permissions(
let auth_id = match auth_id {
Some(auth_id) if auth_id == current_auth_id => current_auth_id,
Some(auth_id) => {
- if user_privs & PRIV_SYS_AUDIT != 0
+ if user_privs & PRIV_SYS_AUDIT != 0
|| (auth_id.is_token()
&& !current_auth_id.is_token()
&& auth_id.user() == current_auth_id.user())
diff --git a/src/config/domains.rs b/src/config/domains.rs
index 7db1f0be..a975fed1 100644
--- a/src/config/domains.rs
+++ b/src/config/domains.rs
@@ -29,6 +29,22 @@ lazy_static! {
pub static ref CONFIG: SectionConfig = init();
}
+#[api()]
+#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+/// Use the value of this attribute/claim as unique user name. It is
+/// up to the identity provider to guarantee the uniqueness. The
+/// OpenID specification only guarantees that Subject ('sub') is unique. Also
+/// make sure that the user is not allowed to change that attribute by
+/// himself!
+pub enum OpenIdUserAttribute {
+ /// Subject (OpenId 'sub' claim)
+ Subject,
+ /// Username (OpenId 'preferred_username' claim)
+ Username,
+ /// Email (OpenId 'email' claim)
+ Email,
+}
#[api(
properties: {
@@ -58,6 +74,10 @@ lazy_static! {
type: bool,
default: false,
},
+ "user-attr": {
+ type: OpenIdUserAttribute,
+ optional: true,
+ },
},
)]
#[derive(Serialize,Deserialize)]
@@ -73,6 +93,8 @@ pub struct OpenIdRealmConfig {
pub comment: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub autocreate: Option<bool>,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub user_attr: Option<OpenIdUserAttribute>,
}
impl OpenIdRealmConfig {
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread