1 //! OpenID redirect/login API
2 use std
::convert
::TryFrom
;
4 use anyhow
::{bail, Error}
;
6 use serde_json
::{json, Value}
;
8 use proxmox
::api
::router
::{Router, SubdirMap}
;
9 use proxmox
::api
::{api, Permission, RpcEnvironment}
;
10 use proxmox
::{list_subdirs_api_method}
;
11 use proxmox
::{identity, sortable}
;
13 use proxmox_openid
::{OpenIdAuthenticator, OpenIdConfig}
;
15 use pbs_buildcfg
::PROXMOX_BACKUP_RUN_DIR_M
;
16 use pbs_tools
::auth
::private_auth_key
;
17 use pbs_tools
::ticket
::Ticket
;
19 use crate::server
::ticket
::ApiTicket
;
21 use crate::config
::domains
::{OpenIdUserAttribute, OpenIdRealmConfig}
;
22 use crate::config
::cached_user_info
::CachedUserInfo
;
24 use crate::backup
::open_backup_lockfile
;
26 use crate::api2
::types
::*;
27 use crate::auth_helpers
::*;
29 fn openid_authenticator(realm_config
: &OpenIdRealmConfig
, redirect_url
: &str) -> Result
<OpenIdAuthenticator
, Error
> {
30 let config
= OpenIdConfig
{
31 issuer_url
: realm_config
.issuer_url
.clone(),
32 client_id
: realm_config
.client_id
.clone(),
33 client_key
: realm_config
.client_key
.clone(),
35 OpenIdAuthenticator
::discover(&config
, redirect_url
)
43 description
: "OpenId state.",
47 description
: "OpenId authorization code.",
51 description
: "Redirection Url. The client should set this to used server url.",
60 description
: "User name.",
64 description
: "Auth ticket.",
66 CSRFPreventionToken
: {
68 description
: "Cross Site Request Forgery Prevention Token.",
74 permission
: &Permission
::World
,
77 /// Verify OpenID authorization code and create a ticket
79 /// Returns: An authentication ticket with additional infos.
84 _rpcenv
: &mut dyn RpcEnvironment
,
85 ) -> Result
<Value
, Error
> {
86 let user_info
= CachedUserInfo
::new()?
;
88 let (realm
, private_auth_state
) =
89 OpenIdAuthenticator
::verify_public_auth_state(PROXMOX_BACKUP_RUN_DIR_M
!(), &state
)?
;
91 let (domains
, _digest
) = crate::config
::domains
::config()?
;
92 let config
: OpenIdRealmConfig
= domains
.lookup("openid", &realm
)?
;
94 let open_id
= openid_authenticator(&config
, &redirect_url
)?
;
96 let info
= open_id
.verify_authorization_code(&code
, &private_auth_state
)?
;
98 // eprintln!("VERIFIED {} {:?} {:?}", info.subject().as_str(), info.name(), info.email());
100 let unique_name
= match config
.username_claim
{
101 None
| Some(OpenIdUserAttribute
::Subject
) => info
.subject().as_str(),
102 Some(OpenIdUserAttribute
::Username
) => {
103 match info
.preferred_username() {
104 Some(name
) => name
.as_str(),
105 None
=> bail
!("missing claim 'preferred_name'"),
108 Some(OpenIdUserAttribute
::Email
) => {
110 Some(name
) => name
.as_str(),
111 None
=> bail
!("missing claim 'email'"),
116 let user_id
= Userid
::try_from(format
!("{}@{}", unique_name
, realm
))?
;
118 if !user_info
.is_active_user_id(&user_id
) {
119 if config
.autocreate
.unwrap_or(false) {
120 use crate::config
::user
;
121 let _lock
= open_backup_lockfile(user
::USER_CFG_LOCKFILE
, None
, true)?
;
122 let user
= user
::User
{
123 userid
: user_id
.clone(),
127 firstname
: info
.given_name().and_then(|n
| n
.get(None
)).map(|n
| n
.to_string()),
128 lastname
: info
.family_name().and_then(|n
| n
.get(None
)).map(|n
| n
.to_string()),
129 email
: info
.email().map(|e
| e
.to_string()),
131 let (mut config
, _digest
) = user
::config()?
;
132 if config
.sections
.get(user
.userid
.as_str()).is_some() {
133 bail
!("autocreate user failed - '{}' already exists.", user
.userid
);
135 config
.set_data(user
.userid
.as_str(), "user", &user
)?
;
136 user
::save_config(&config
)?
;
138 bail
!("user account '{}' missing, disabled or expired.", user_id
);
142 let api_ticket
= ApiTicket
::full(user_id
.clone());
143 let ticket
= Ticket
::new("PBS", &api_ticket
)?
.sign(private_auth_key(), None
)?
;
144 let token
= assemble_csrf_prevention_token(csrf_secret(), &user_id
);
146 crate::server
::rest
::auth_logger()?
147 .log(format
!("successful auth for user '{}'", user_id
));
152 "CSRFPreventionToken": token
,
161 schema
: REALM_ID_SCHEMA
,
164 description
: "Redirection Url. The client should set this to used server url.",
170 description
: "Redirection URL.",
174 description
: "Anyone can access this (before the user is authenticated).",
175 permission
: &Permission
::World
,
178 /// Create OpenID Redirect Session
181 redirect_url
: String
,
182 _rpcenv
: &mut dyn RpcEnvironment
,
183 ) -> Result
<String
, Error
> {
185 let (domains
, _digest
) = crate::config
::domains
::config()?
;
186 let config
: OpenIdRealmConfig
= domains
.lookup("openid", &realm
)?
;
188 let open_id
= openid_authenticator(&config
, &redirect_url
)?
;
190 let url
= open_id
.authorize_url(PROXMOX_BACKUP_RUN_DIR_M
!(), &realm
)?
197 const SUBDIRS
: SubdirMap
= &sorted
!([
198 ("login", &Router
::new().post(&API_METHOD_OPENID_LOGIN
)),
199 ("auth-url", &Router
::new().post(&API_METHOD_OPENID_AUTH_URL
)),
202 pub const ROUTER
: Router
= Router
::new()
203 .get(&list_subdirs_api_method
!(SUBDIRS
))