* [pbs-devel] [PATH proxmox-backup v1 00/12] OpenID connect realms
@ 2021-06-22 8:56 Dietmar Maurer
2021-06-22 8:56 ` [pbs-devel] [PATH proxmox-backup v1 01/12] depend on openid-connect-rs Dietmar Maurer
` (11 more replies)
0 siblings, 12 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-22 8:56 UTC (permalink / raw)
To: pbs-devel
This implements OpenID connect realms using the new
"proxmox-openid-rs" crate.
Changes since preview version (for Fabian):
- fix commit message
- reserve namen 'pam' and 'pbs'
- fix 'make deb'
Dietmar Maurer (12):
depend on openid-connect-rs
config: new domains.cfg to configure openid realm
check_acl_path: add /access/domains
add API to manage openid realms
cli: add CLI to manage openid realms.
api: add openid redirect API
implement new helper is_active_user_id()
api: add openid-login endpoint
ui: implement OpenId login
cleanup user/token is_active() check
add openid autocreate account feature
implement openid user-attr configuration
Cargo.toml | 2 +
src/api2/access.rs | 127 ++++++++++-
src/api2/access/domain.rs | 62 ++++-
src/api2/config/access/mod.rs | 8 +-
src/api2/config/access/openid.rs | 274 +++++++++++++++++++++++
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.rs | 1 +
src/config/acl.rs | 2 +-
src/config/cached_user_info.rs | 35 ++-
src/config/domains.rs | 175 +++++++++++++++
src/config/user.rs | 32 +++
www/Application.js | 8 +-
www/LoginView.js | 100 ++++++++-
www/Utils.js | 8 +
16 files changed, 908 insertions(+), 28 deletions(-)
create mode 100644 src/api2/config/access/openid.rs
create mode 100644 src/bin/proxmox_backup_manager/openid.rs
create mode 100644 src/config/domains.rs
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [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
end of thread, other threads:[~2021-06-22 8:57 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [pbs-devel] [PATH proxmox-backup v1 03/12] check_acl_path: add /access/domains Dietmar Maurer
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 ` [pbs-devel] [PATH proxmox-backup v1 05/12] cli: add CLI " Dietmar Maurer
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 ` [pbs-devel] [PATH proxmox-backup v1 07/12] implement new helper is_active_user_id() Dietmar Maurer
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 ` [pbs-devel] [PATH proxmox-backup v1 09/12] ui: implement OpenId login Dietmar Maurer
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 ` [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
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