mesa/common/
authentication.rs1use directories::ProjectDirs;
2use serde_json::Value;
3
4use dialoguer::{Input, Password};
5use std::{
6 collections::HashMap,
7 fs::{create_dir_all, File},
8 io::{Read, Write},
9 path::PathBuf,
10 time::Duration,
11};
12
13use termion::color;
14
15use crate::error::Error;
16
17pub async fn get_api_token(
20 shasta_base_url: &str,
21 shasta_root_cert: &[u8],
22 keycloak_base_url: &str,
23 site_name: &str,
24) -> Result<String, Error> {
25 let mut shasta_token: String;
26
27 for (env, value) in std::env::vars() {
29 if env.eq_ignore_ascii_case("MANTA_CSM_TOKEN") {
30 log::info!(
31 "Looking for CSM authentication token in envonment variable 'MANTA_CSM_TOKEN'"
32 );
33
34 shasta_token = value;
35
36 match test_client_api(shasta_base_url, &shasta_token, shasta_root_cert).await {
37 Ok(_) => return Ok(shasta_token),
38 Err(_) => return Err(Error::Message("Authentication unsucessful".to_string())),
39 }
40 }
41 }
42
43 log::info!("Looking for CSM authentication token in filesystem file");
45
46 let mut file;
47
48 let project_dirs = ProjectDirs::from(
49 "local", "cscs", "manta", );
53
54 let mut path = PathBuf::from(project_dirs.unwrap().cache_dir());
55
56 let mut attempts = 0;
57
58 create_dir_all(&path)?;
59
60 path.push(site_name.to_string() + "_auth"); log::debug!("Cache file: {:?}", path);
63
64 shasta_token = if path.exists() {
65 get_token_from_local_file(path.as_os_str()).unwrap()
66 } else {
67 String::new()
68 };
69
70 while !test_client_api(shasta_base_url, &shasta_token, shasta_root_cert)
71 .await
72 .unwrap()
73 && attempts < 3
74 {
75 println!(
76 "Please type your {}Keycloak credentials{}",
77 color::Fg(color::Green),
78 color::Fg(color::Reset)
79 );
80 let username: String = Input::new().with_prompt("username").interact_text()?;
81 let password = Password::new().with_prompt("password").interact()?;
82
83 match get_token_from_shasta_endpoint(
84 keycloak_base_url,
85 shasta_root_cert,
86 &username,
87 &password,
88 )
89 .await
90 {
91 Ok(shasta_token_aux) => {
92 log::debug!("Shasta token received");
93 file = File::create(&path).expect("Error encountered while creating file!");
94 file.write_all(shasta_token_aux.as_bytes())
95 .expect("Error while writing to file");
96 shasta_token = get_token_from_local_file(path.as_os_str()).unwrap();
97 }
98 Err(_) => {
99 eprintln!("Failed in getting token from Shasta API");
100 }
101 }
102
103 attempts += 1;
104 }
105
106 if attempts < 3 {
107 shasta_token = get_token_from_local_file(path.as_os_str()).unwrap();
108 Ok(shasta_token)
109 } else {
110 Err(Error::Message("Authentication unsucessful".to_string())) }
112}
113
114pub fn get_token_from_local_file(path: &std::ffi::OsStr) -> Result<String, reqwest::Error> {
115 let mut shasta_token = String::new();
116 File::open(path)
117 .unwrap()
118 .read_to_string(&mut shasta_token)
119 .unwrap();
120 Ok(shasta_token.to_string())
121}
122
123pub async fn test_connectivity_to_backend(shasta_base_url: &str) -> bool {
124 let client;
125
126 let client_builder = reqwest::Client::builder().connect_timeout(Duration::new(3, 0));
127
128 client = client_builder.build().unwrap();
130
131 let api_url = shasta_base_url.to_owned() + "/cfs/healthz";
132
133 log::info!("Validate Shasta token against {}", api_url);
134
135 let resp_rslt = client.get(api_url).send().await;
136
137 match resp_rslt {
138 Ok(_) => true,
139 Err(error) => !error.is_timeout(),
140 }
141}
142
143pub async fn test_client_api(
144 shasta_base_url: &str,
145 shasta_token: &str,
146 shasta_root_cert: &[u8],
147) -> Result<bool, reqwest::Error> {
148 let client;
149
150 let client_builder = reqwest::Client::builder()
151 .add_root_certificate(reqwest::Certificate::from_pem(shasta_root_cert)?);
152
153 if std::env::var("SOCKS5").is_ok() {
155 log::debug!("SOCKS5 enabled");
157 let socks5proxy = reqwest::Proxy::all(std::env::var("SOCKS5").unwrap())?;
158
159 client = client_builder.proxy(socks5proxy).build()?;
161 } else {
162 client = client_builder.build()?;
163 }
164
165 let api_url = shasta_base_url.to_owned() + "/cfs/healthz";
166
167 log::info!("Validate Shasta token against {}", api_url);
168
169 let resp_rslt = client.get(api_url).bearer_auth(shasta_token).send().await;
170
171 match resp_rslt {
172 Ok(resp) => {
173 if resp.status().is_success() {
174 log::info!("Shasta token is valid");
175 return Ok(true);
176 } else {
177 let payload = resp.text().await?;
178 log::error!("Token is not valid - {}", payload);
179 return Ok(false);
180 }
181 }
182 Err(error) => {
183 eprintln!("Error connecting to Shasta API. Reason:\n{:?}. Exit", error);
184 log::debug!("Response:\n{:#?}", error);
185 std::process::exit(1);
186 }
187 }
188}
189
190pub async fn get_token_from_shasta_endpoint(
191 keycloak_base_url: &str,
192 shasta_root_cert: &[u8],
193 username: &str,
194 password: &str,
195) -> Result<String, reqwest::Error> {
196 let mut params = HashMap::new();
197 params.insert("grant_type", "password");
198 params.insert("client_id", "shasta");
199 params.insert("username", username);
200 params.insert("password", password);
201
202 let client;
203
204 let client_builder = reqwest::Client::builder()
205 .add_root_certificate(reqwest::Certificate::from_pem(shasta_root_cert)?);
206
207 if std::env::var("SOCKS5").is_ok() {
209 let socks5proxy = reqwest::Proxy::all(std::env::var("SOCKS5").unwrap())?;
211
212 client = client_builder.proxy(socks5proxy).build()?;
214 } else {
215 client = client_builder.build()?;
216 }
217
218 let api_url = format!(
219 "{}/realms/shasta/protocol/openid-connect/token",
220 keycloak_base_url
221 );
222
223 log::debug!("Request to fetch authentication token: {}", api_url);
224
225 Ok(client
226 .post(api_url)
227 .form(¶ms)
228 .send()
229 .await?
230 .error_for_status()?
231 .json::<Value>()
232 .await?["access_token"]
233 .as_str()
234 .unwrap()
235 .to_string())
236}