]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/access/openid.rs
move more tools for the client into subcrates
[proxmox-backup.git] / src / api2 / access / openid.rs
CommitLineData
3b7b1dfb
DM
1//! OpenID redirect/login API
2use std::convert::TryFrom;
3
4use anyhow::{bail, Error};
5
6use serde_json::{json, Value};
7
8use proxmox::api::router::{Router, SubdirMap};
9use proxmox::api::{api, Permission, RpcEnvironment};
10use proxmox::{list_subdirs_api_method};
11use proxmox::{identity, sortable};
12use proxmox::tools::fs::open_file_locked;
13
26a3450f
FG
14use proxmox_openid::{OpenIdAuthenticator, OpenIdConfig};
15
923f94a4 16use pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M;
4805edc4 17use pbs_tools::auth::private_auth_key;
9eb78407 18use pbs_tools::ticket::Ticket;
3b7b1dfb
DM
19
20use crate::server::ticket::ApiTicket;
3b7b1dfb
DM
21
22use crate::config::domains::{OpenIdUserAttribute, OpenIdRealmConfig};
23use crate::config::cached_user_info::CachedUserInfo;
24
25use crate::api2::types::*;
26use crate::auth_helpers::*;
27
26a3450f
FG
28fn openid_authenticator(realm_config: &OpenIdRealmConfig, redirect_url: &str) -> Result<OpenIdAuthenticator, Error> {
29 let config = OpenIdConfig {
30 issuer_url: realm_config.issuer_url.clone(),
31 client_id: realm_config.client_id.clone(),
32 client_key: realm_config.client_key.clone(),
33 };
34 OpenIdAuthenticator::discover(&config, redirect_url)
35}
36
37
3b7b1dfb
DM
38#[api(
39 input: {
40 properties: {
41 state: {
42 description: "OpenId state.",
43 type: String,
44 },
45 code: {
46 description: "OpenId authorization code.",
47 type: String,
48 },
49 "redirect-url": {
50 description: "Redirection Url. The client should set this to used server url.",
51 type: String,
52 },
53 },
54 },
55 returns: {
56 properties: {
57 username: {
58 type: String,
59 description: "User name.",
60 },
61 ticket: {
62 type: String,
63 description: "Auth ticket.",
64 },
65 CSRFPreventionToken: {
66 type: String,
67 description: "Cross Site Request Forgery Prevention Token.",
68 },
69 },
70 },
71 protected: true,
72 access: {
73 permission: &Permission::World,
74 },
75)]
76/// Verify OpenID authorization code and create a ticket
77///
78/// Returns: An authentication ticket with additional infos.
79pub fn openid_login(
80 state: String,
81 code: String,
82 redirect_url: String,
83 _rpcenv: &mut dyn RpcEnvironment,
84) -> Result<Value, Error> {
85 let user_info = CachedUserInfo::new()?;
86
87 let (realm, private_auth_state) =
88 OpenIdAuthenticator::verify_public_auth_state(PROXMOX_BACKUP_RUN_DIR_M!(), &state)?;
89
90 let (domains, _digest) = crate::config::domains::config()?;
91 let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
92
26a3450f 93 let open_id = openid_authenticator(&config, &redirect_url)?;
3b7b1dfb
DM
94
95 let info = open_id.verify_authorization_code(&code, &private_auth_state)?;
96
97 // eprintln!("VERIFIED {} {:?} {:?}", info.subject().as_str(), info.name(), info.email());
98
99 let unique_name = match config.username_claim {
100 None | Some(OpenIdUserAttribute::Subject) => info.subject().as_str(),
101 Some(OpenIdUserAttribute::Username) => {
102 match info.preferred_username() {
103 Some(name) => name.as_str(),
104 None => bail!("missing claim 'preferred_name'"),
105 }
106 }
107 Some(OpenIdUserAttribute::Email) => {
108 match info.email() {
109 Some(name) => name.as_str(),
110 None => bail!("missing claim 'email'"),
111 }
112 }
113 };
114
115 let user_id = Userid::try_from(format!("{}@{}", unique_name, realm))?;
116
117 if !user_info.is_active_user_id(&user_id) {
118 if config.autocreate.unwrap_or(false) {
119 use crate::config::user;
120 let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
121 let user = user::User {
122 userid: user_id.clone(),
123 comment: None,
124 enable: None,
125 expire: None,
126 firstname: info.given_name().and_then(|n| n.get(None)).map(|n| n.to_string()),
127 lastname: info.family_name().and_then(|n| n.get(None)).map(|n| n.to_string()),
128 email: info.email().map(|e| e.to_string()),
129 };
130 let (mut config, _digest) = user::config()?;
131 if config.sections.get(user.userid.as_str()).is_some() {
132 bail!("autocreate user failed - '{}' already exists.", user.userid);
133 }
134 config.set_data(user.userid.as_str(), "user", &user)?;
135 user::save_config(&config)?;
3b7b1dfb
DM
136 } else {
137 bail!("user account '{}' missing, disabled or expired.", user_id);
138 }
139 }
140
141 let api_ticket = ApiTicket::full(user_id.clone());
142 let ticket = Ticket::new("PBS", &api_ticket)?.sign(private_auth_key(), None)?;
143 let token = assemble_csrf_prevention_token(csrf_secret(), &user_id);
144
145 crate::server::rest::auth_logger()?
146 .log(format!("successful auth for user '{}'", user_id));
147
148 Ok(json!({
149 "username": user_id,
150 "ticket": ticket,
151 "CSRFPreventionToken": token,
152 }))
153}
154
155#[api(
156 protected: true,
157 input: {
158 properties: {
159 realm: {
160 schema: REALM_ID_SCHEMA,
161 },
162 "redirect-url": {
163 description: "Redirection Url. The client should set this to used server url.",
164 type: String,
165 },
166 },
167 },
168 returns: {
169 description: "Redirection URL.",
170 type: String,
171 },
172 access: {
173 description: "Anyone can access this (before the user is authenticated).",
174 permission: &Permission::World,
175 },
176)]
177/// Create OpenID Redirect Session
178fn openid_auth_url(
179 realm: String,
180 redirect_url: String,
181 _rpcenv: &mut dyn RpcEnvironment,
182) -> Result<String, Error> {
183
184 let (domains, _digest) = crate::config::domains::config()?;
185 let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
186
26a3450f 187 let open_id = openid_authenticator(&config, &redirect_url)?;
3b7b1dfb
DM
188
189 let url = open_id.authorize_url(PROXMOX_BACKUP_RUN_DIR_M!(), &realm)?
190 .to_string();
191
192 Ok(url.into())
193}
194
195#[sortable]
196const SUBDIRS: SubdirMap = &sorted!([
197 ("login", &Router::new().post(&API_METHOD_OPENID_LOGIN)),
198 ("auth-url", &Router::new().post(&API_METHOD_OPENID_AUTH_URL)),
199]);
200
201pub const ROUTER: Router = Router::new()
202 .get(&list_subdirs_api_method!(SUBDIRS))
203 .subdirs(SUBDIRS);