2019-03-29 22:18:25 +00:00
|
|
|
extern crate reqwest;
|
2019-04-05 18:49:32 +00:00
|
|
|
extern crate serde;
|
2019-03-29 22:18:25 +00:00
|
|
|
|
|
|
|
use reqwest::Response;
|
2019-04-05 18:49:32 +00:00
|
|
|
use serde::Deserialize;
|
2019-03-29 22:18:25 +00:00
|
|
|
use std::collections::HashMap;
|
2019-04-05 18:49:32 +00:00
|
|
|
use std::error::Error;
|
2019-03-29 22:18:25 +00:00
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
|
|
|
|
const COOKIE_LIFESPAN: Duration = Duration::from_secs(20 * 60);
|
|
|
|
|
2019-04-11 23:20:00 +00:00
|
|
|
fn true_val() -> bool {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-04-05 18:49:32 +00:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct User {
|
2019-04-11 23:20:00 +00:00
|
|
|
#[serde(rename = "Email")]
|
|
|
|
email: String,
|
2019-04-05 18:49:32 +00:00
|
|
|
#[serde(rename = "_Enabled")]
|
2019-04-11 23:20:00 +00:00
|
|
|
#[serde(default = "true_val")]
|
|
|
|
enabled: bool,
|
2019-04-05 18:49:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl User {
|
|
|
|
pub fn get_email(&self) -> String {
|
2019-04-11 23:20:00 +00:00
|
|
|
self.email.clone()
|
2019-04-05 18:49:32 +00:00
|
|
|
}
|
|
|
|
pub fn is_enabled(&self) -> bool {
|
2019-04-11 23:20:00 +00:00
|
|
|
self.enabled
|
2019-04-05 18:49:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:18:25 +00:00
|
|
|
pub struct Client {
|
|
|
|
url: String,
|
|
|
|
admin_token: String,
|
|
|
|
cookie: Option<String>,
|
|
|
|
cookie_created: Option<Instant>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Create new instance of client
|
2019-03-29 22:18:25 +00:00
|
|
|
pub fn new(url: String, admin_token: String) -> Client {
|
|
|
|
Client {
|
|
|
|
url,
|
|
|
|
admin_token,
|
|
|
|
cookie: None,
|
|
|
|
cookie_created: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Authenticate client
|
2019-03-29 22:18:25 +00:00
|
|
|
fn auth(&mut self) -> Response {
|
|
|
|
let cookie_created = Instant::now();
|
2019-03-29 22:40:26 +00:00
|
|
|
let client = reqwest::Client::builder()
|
|
|
|
// Avoid redirects because server will redirect to admin page after auth
|
|
|
|
.redirect(reqwest::RedirectPolicy::none())
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
let result = client
|
2019-03-29 22:18:25 +00:00
|
|
|
.post(format!("{}{}", &self.url, "/admin/").as_str())
|
|
|
|
.form(&[("token", &self.admin_token)])
|
|
|
|
.send()
|
|
|
|
.unwrap_or_else(|e| {
|
|
|
|
panic!("Could not authenticate with {}. {:?}", &self.url, e);
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO: Handle error statuses
|
|
|
|
|
|
|
|
if let Some(cookie) = result.headers().get(reqwest::header::SET_COOKIE) {
|
|
|
|
self.cookie = cookie.to_str().map(|s| String::from(s)).ok();
|
|
|
|
self.cookie_created = Some(cookie_created);
|
|
|
|
} else {
|
2019-03-29 22:40:26 +00:00
|
|
|
panic!("Could not authenticate.")
|
2019-03-29 22:18:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Ensure that the client has a current auth cookie
|
2019-03-29 22:18:25 +00:00
|
|
|
fn ensure_auth(&mut self) {
|
|
|
|
match &self.cookie {
|
|
|
|
Some(_) => {
|
|
|
|
if self
|
|
|
|
.cookie_created
|
|
|
|
.map_or(true, |created| (created.elapsed() >= COOKIE_LIFESPAN))
|
|
|
|
{
|
2019-03-29 22:40:26 +00:00
|
|
|
self.auth();
|
2019-03-29 22:18:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
2019-03-29 22:40:26 +00:00
|
|
|
self.auth();
|
2019-03-29 22:18:25 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// TODO: handle errors
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Make an authenticated GET to Bitwarden Admin
|
2019-03-29 22:18:25 +00:00
|
|
|
fn get(&mut self, path: &str) -> Response {
|
|
|
|
self.ensure_auth();
|
|
|
|
|
|
|
|
match &self.cookie {
|
|
|
|
None => {
|
|
|
|
panic!("We haven't authenticated. Must be an error");
|
|
|
|
}
|
|
|
|
Some(cookie) => {
|
|
|
|
let url = format!("{}/admin{}", &self.url, path);
|
|
|
|
let request = reqwest::Client::new()
|
|
|
|
.get(url.as_str())
|
|
|
|
.header(reqwest::header::COOKIE, cookie.clone());
|
|
|
|
let response = request.send().unwrap_or_else(|e| {
|
|
|
|
panic!("Could not call with {}. {:?}", url, e);
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO: Handle error statuses
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Make authenticated POST to Bitwarden Admin with JSON data
|
2019-03-29 22:18:25 +00:00
|
|
|
fn post(&mut self, path: &str, json: &HashMap<String, String>) -> Response {
|
|
|
|
self.ensure_auth();
|
|
|
|
|
|
|
|
match &self.cookie {
|
|
|
|
None => {
|
|
|
|
panic!("We haven't authenticated. Must be an error");
|
|
|
|
}
|
|
|
|
Some(cookie) => {
|
|
|
|
let url = format!("{}/admin{}", &self.url, path);
|
|
|
|
let request = reqwest::Client::new()
|
|
|
|
.post(url.as_str())
|
|
|
|
.header("Cookie", cookie.clone())
|
|
|
|
.json(&json);
|
|
|
|
let response = request.send().unwrap_or_else(|e| {
|
|
|
|
panic!("Could not call with {}. {:?}", url, e);
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO: Handle error statuses
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-29 22:40:26 +00:00
|
|
|
/// Invite user with provided email
|
2019-03-29 22:18:25 +00:00
|
|
|
pub fn invite(&mut self, email: &str) -> Response {
|
|
|
|
let mut json = HashMap::new();
|
|
|
|
json.insert("email".to_string(), email.to_string());
|
|
|
|
|
|
|
|
self.post("/invite", &json)
|
|
|
|
}
|
2019-04-05 18:49:32 +00:00
|
|
|
|
|
|
|
/// Get all existing users
|
|
|
|
pub fn users(&mut self) -> Result<Vec<User>, Box<Error>> {
|
|
|
|
let all_users: Vec<User> = self.get("/users").json()?;
|
|
|
|
Ok(all_users)
|
|
|
|
}
|
2019-04-11 23:20:00 +00:00
|
|
|
|
|
|
|
/// Get all invited users
|
|
|
|
pub fn invites(&mut self) -> Result<Vec<User>, Box<Error>> {
|
|
|
|
let all_invites: Vec<User> = self.get("/invites").json()?;
|
|
|
|
Ok(all_invites)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get all users and invites
|
|
|
|
pub fn users_and_invites(&mut self) -> Result<Vec<User>, Box<Error>> {
|
|
|
|
let mut all_users = self.users()?;
|
|
|
|
let mut invites = self.invites()?;
|
|
|
|
all_users.append(&mut invites);
|
|
|
|
Ok(all_users)
|
|
|
|
}
|
2019-03-29 22:18:25 +00:00
|
|
|
}
|