Newer
Older
/* Copyright 2021 Dominik George <dominik.george@teckids.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::BASE_NAME;
use lazy_static::lazy_static;
use std::convert::From;
use std::time::SystemTime;
use libc::{geteuid, seteuid, getpwnam, uid_t};
use oauth2::basic::BasicTokenResponse;
use std::fs::remove_file;
use std::io;
use std::path::PathBuf;
use xdg::{BaseDirectories,BaseDirectoriesError};
const TOKEN_DEFAULT_EXPIRES: u64 = 24 * 60 * 60;
const USER_TOKEN_FILENAME: &str = "user_token.json";
struct UserToken {
access_token: String,
expires_at: u64,
refresh_token: Option<String>,
}
impl UserToken {
fn is_expired(&self) -> bool {
match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(d) => d.as_secs() >= self.expires_at,
Err(_) => true
}
}
}
impl From<BasicTokenResponse> for UserToken {
fn from(response: BasicTokenResponse) -> Self {
UserToken {
access_token: response.access_token.secret().to_string(),
expires_at: match response.expires_in {
Some(duration) => duration,
None => TOKEN_DEFAULT_EXPIRES
} + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).ok().unwrap().as_secs(),
refresh_token: match response.refresh_token {
Some(t) => Some(t.secret().to_string()),
None => None
}
}
}
user_tokens: HashMap<String, UserToken>,
}
impl Cache {
user_tokens: HashMap::new(),
original_euid: geteuid()
fn drop_privileges(&self, username: String) -> Result<uid_t, String> {
let nam = match CString::new(username) {
Ok(nam) => nam,
Err(_) => return Err("Invalid username in lookup".to_string())
};
let target_euid = (*getpwnam(nam.as_ptr())).pw_uid;
if target_euid == self.original_euid {
return Ok(self.original_euid);
} else if self.original_euid == 0 {
seteuid(target_euid);
return Ok(target_euid);
}
return Err("Dropping privileges not supported".to_string());
}
fn restore_privileges(&self) {
seteuid(self.original_euid);
}
fn get_user_xdg_base_directories(&self, username: String) -> Result<BaseDirectories, BaseDirectoriesError> {
let saved_home = env::var_os("HOME");
let nam = match CString::new(username) {
Ok(nam) => nam,
Err(_) => CString::new("nobody").ok().unwrap()
};
let user_home = CString::from_raw((*getpwnam(nam.as_ptr())).pw_dir).to_str().unwrap();
env::set_var("HOME", user_home);
let base_dirs = BaseDirectories::with_prefix(BASE_NAME)?;
if saved_home != None {
env::set_var("HOME", saved_home.unwrap());
} else {
env::remove_var("HOME");
}
return Ok(base_dirs);
}
fn place_user_cache_file(&self, username: String, filename: &str) -> Result<PathBuf, io::Error> {
match self.get_user_xdg_base_directories(username) {
Ok(b) => b.place_cache_file(filename),
Err(e) => Err(io::Error::new(io::ErrorKind::NotFound, e))
}
}
pub fn load_user_token(&self, owner: String) -> Option<&UserToken> {
return self.user_tokens.get(&owner);
}
pub fn save_user_token(&self, owner: String, token: UserToken) {
self.user_tokens.insert(owner, token);
pub fn delete_user_token(&self, owner: String) {
self.user_tokens.remove(&owner);
// Try to remove user's token cache file
self.drop_privileges(owner).ok();
match self.place_user_cache_file(owner, USER_TOKEN_FILENAME) {
Ok(path) => remove_file(path),
Err(e) => Err(e)
};
self.restore_privileges();
for (owner, token) in self.user_tokens {
if token.is_expired() {
self.delete_user_token(owner);
}
}
}
}
lazy_static! {