[pbs-devel] [PATH proxmox-backup v1 08/12] api: add openid-login endpoint

Dietmar Maurer dietmar at proxmox.com
Tue Jun 22 10:56:16 CEST 2021


---
 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





More information about the pbs-devel mailing list