Skip to content
Snippets Groups Projects
cache.rs 4.73 KiB
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 lazy_static::lazy_static;
use std::collections::HashMap;
use std::convert::From;
use std::time::SystemTime;

use libc::{geteuid, seteuid, getpwnam, uid_t};
use std::ffi::CString;

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>,
}

    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>,
    original_euid: uid_t,
    pub fn new() -> 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();
    pub fn cleanup_tokens(&self) {
        for (owner, token) in self.user_tokens {
            if token.is_expired() {
                self.delete_user_token(owner);
    pub static ref CACHE: Cache = Cache::new();