]>
Commit | Line | Data |
---|---|---|
bbff6c49 DM |
1 | use anyhow::{Error}; |
2 | use lazy_static::lazy_static; | |
3 | use std::collections::HashMap; | |
4 | use serde::{Serialize, Deserialize}; | |
5 | ||
6 | use proxmox::api::{ | |
7 | api, | |
8 | schema::*, | |
9 | section_config::{ | |
10 | SectionConfig, | |
11 | SectionConfigData, | |
12 | SectionConfigPlugin, | |
13 | } | |
14 | }; | |
15 | ||
16 | use proxmox::tools::fs::{ | |
bbff6c49 DM |
17 | replace_file, |
18 | CreateOptions, | |
19 | }; | |
20 | ||
21 | use crate::api2::types::*; | |
7526d864 | 22 | use crate::backup::{open_backup_lockfile, BackupLockGuard}; |
bbff6c49 DM |
23 | |
24 | lazy_static! { | |
25 | pub static ref CONFIG: SectionConfig = init(); | |
26 | } | |
27 | ||
3b7b1dfb DM |
28 | #[api()] |
29 | #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] | |
30 | #[serde(rename_all = "lowercase")] | |
31 | /// Use the value of this attribute/claim as unique user name. It is | |
32 | /// up to the identity provider to guarantee the uniqueness. The | |
33 | /// OpenID specification only guarantees that Subject ('sub') is unique. Also | |
34 | /// make sure that the user is not allowed to change that attribute by | |
35 | /// himself! | |
36 | pub enum OpenIdUserAttribute { | |
37 | /// Subject (OpenId 'sub' claim) | |
38 | Subject, | |
39 | /// Username (OpenId 'preferred_username' claim) | |
40 | Username, | |
41 | /// Email (OpenId 'email' claim) | |
42 | Email, | |
43 | } | |
bbff6c49 DM |
44 | |
45 | #[api( | |
46 | properties: { | |
47 | realm: { | |
48 | schema: REALM_ID_SCHEMA, | |
49 | }, | |
50 | "issuer-url": { | |
51 | description: "OpenID Issuer Url", | |
52 | type: String, | |
53 | }, | |
54 | "client-id": { | |
55 | description: "OpenID Client ID", | |
56 | type: String, | |
57 | }, | |
58 | "client-key": { | |
59 | description: "OpenID Client Key", | |
60 | type: String, | |
61 | optional: true, | |
62 | }, | |
63 | comment: { | |
64 | optional: true, | |
65 | schema: SINGLE_LINE_COMMENT_SCHEMA, | |
66 | }, | |
3b7b1dfb DM |
67 | autocreate: { |
68 | description: "Automatically create users if they do not exist.", | |
69 | optional: true, | |
70 | type: bool, | |
71 | default: false, | |
72 | }, | |
73 | "username-claim": { | |
74 | type: OpenIdUserAttribute, | |
75 | optional: true, | |
76 | }, | |
bbff6c49 DM |
77 | }, |
78 | )] | |
79 | #[derive(Serialize,Deserialize)] | |
80 | #[serde(rename_all="kebab-case")] | |
81 | /// OpenID configuration properties. | |
82 | pub struct OpenIdRealmConfig { | |
83 | pub realm: String, | |
84 | pub issuer_url: String, | |
85 | pub client_id: String, | |
86 | #[serde(skip_serializing_if="Option::is_none")] | |
87 | pub client_key: Option<String>, | |
88 | #[serde(skip_serializing_if="Option::is_none")] | |
89 | pub comment: Option<String>, | |
3b7b1dfb DM |
90 | #[serde(skip_serializing_if="Option::is_none")] |
91 | pub autocreate: Option<bool>, | |
92 | #[serde(skip_serializing_if="Option::is_none")] | |
93 | pub username_claim: Option<OpenIdUserAttribute>, | |
94 | } | |
95 | ||
bbff6c49 DM |
96 | fn init() -> SectionConfig { |
97 | let obj_schema = match OpenIdRealmConfig::API_SCHEMA { | |
98 | Schema::Object(ref obj_schema) => obj_schema, | |
99 | _ => unreachable!(), | |
100 | }; | |
101 | ||
102 | let plugin = SectionConfigPlugin::new("openid".to_string(), Some(String::from("realm")), obj_schema); | |
103 | let mut config = SectionConfig::new(&REALM_ID_SCHEMA); | |
104 | config.register_plugin(plugin); | |
105 | ||
106 | config | |
107 | } | |
108 | ||
109 | pub const DOMAINS_CFG_FILENAME: &str = "/etc/proxmox-backup/domains.cfg"; | |
110 | pub const DOMAINS_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.domains.lck"; | |
111 | ||
112 | /// Get exclusive lock | |
7526d864 DM |
113 | pub fn lock_config() -> Result<BackupLockGuard, Error> { |
114 | open_backup_lockfile(DOMAINS_CFG_LOCKFILE, None, true) | |
bbff6c49 DM |
115 | } |
116 | ||
117 | pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | |
118 | ||
119 | let content = proxmox::tools::fs::file_read_optional_string(DOMAINS_CFG_FILENAME)? | |
120 | .unwrap_or_else(|| "".to_string()); | |
121 | ||
122 | let digest = openssl::sha::sha256(content.as_bytes()); | |
123 | let data = CONFIG.parse(DOMAINS_CFG_FILENAME, &content)?; | |
124 | Ok((data, digest)) | |
125 | } | |
126 | ||
127 | pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { | |
128 | let raw = CONFIG.write(DOMAINS_CFG_FILENAME, &config)?; | |
129 | ||
130 | let backup_user = crate::backup::backup_user()?; | |
131 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); | |
132 | // set the correct owner/group/permissions while saving file | |
133 | // owner(rw) = root, group(r)= backup | |
134 | let options = CreateOptions::new() | |
135 | .perm(mode) | |
136 | .owner(nix::unistd::ROOT) | |
137 | .group(backup_user.gid); | |
138 | ||
139 | replace_file(DOMAINS_CFG_FILENAME, raw.as_bytes(), options)?; | |
140 | ||
141 | Ok(()) | |
142 | } | |
143 | ||
144 | // shell completion helper | |
145 | pub fn complete_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | |
146 | match config() { | |
147 | Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), | |
148 | Err(_) => return vec![], | |
149 | } | |
150 | } | |
0decd11e DM |
151 | |
152 | pub fn complete_openid_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | |
153 | match config() { | |
154 | Ok((data, _digest)) => data.sections.iter() | |
155 | .filter_map(|(id, (t, _))| if t == "openid" { Some(id.to_string()) } else { None }) | |
156 | .collect(), | |
157 | Err(_) => return vec![], | |
158 | } | |
159 | } |