use std::convert::From;
use reqwest::{blocking::Client, StatusCode};
use crate::error::InternalError;
use crate::rest_api::SPLINTER_PROTOCOL_VERSION;
use super::{Authorization, BiomeClient, Credentials, Key, NewKey, Profile, UpdateUser};
const PAGING_LIMIT: u32 = 100;
#[derive(Deserialize)]
struct ServerError {
pub message: String,
}
pub struct ReqwestBiomeClient {
url: String,
auth: Option<String>,
}
impl ReqwestBiomeClient {
pub fn new(url: String) -> Self {
ReqwestBiomeClient { url, auth: None }
}
pub fn add_auth(&mut self, auth: String) {
self.auth = Some(auth);
}
pub fn auth(&self) -> Result<String, InternalError> {
match &self.auth {
Some(auth) => Ok(auth.into()),
None => Err(InternalError::with_message(
"ReqwestBiomeClient does not have authorization".into(),
)),
}
}
}
impl BiomeClient for ReqwestBiomeClient {
fn register(&self, username: &str, password: &str) -> Result<Credentials, InternalError> {
let request = Client::new()
.post(&format!("{}/biome/register", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.json(&json!({
"username": username,
"hashed_password": password,
}));
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to register Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
let data: ClientCredentialsResponse =
res.json::<ClientCredentialsResponse>().map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})?;
Ok(Credentials::from(data.data))
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome register request failed with status code '{}', but error \
response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to register Biome user: {}",
message
)))
}
})
}
fn login(&self, username: &str, password: &str) -> Result<Authorization, InternalError> {
let request = Client::new()
.post(&format!("{}/biome/login", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.json(&json!({
"username": username,
"hashed_password": password,
}));
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to login Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
res.json::<ClientAuthorization>()
.map(Authorization::from)
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome login request failed with status code '{}', but error \
response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to login Biome user: {}",
message
)))
}
})
}
fn logout(&self) -> Result<(), InternalError> {
let request = Client::new()
.patch(&format!("{}/biome/logout", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to logout Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
Ok(())
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome logout request failed with status code '{}', but error \
response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to logout Biome user: {}",
message
)))
}
})
}
fn get_new_access_token(&self, refresh_token: &str) -> Result<String, InternalError> {
let request = Client::new()
.post(&format!("{}/biome/token", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?)
.json(&json!({ "token": refresh_token }));
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to get new access token for Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
Ok(res
.json::<ClientAccessToken>()
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})?
.token)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome access token request failed with status code '{}', but \
error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to get new access token for Biome user: {}",
message
)))
}
})
}
fn verify(&self, username: &str, password: &str) -> Result<(), InternalError> {
let request = Client::new()
.post(&format!("{}/biome/verify", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?)
.json(&json!({"username": username, "hashed_password": password}));
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to verify Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
Ok(())
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome verify request failed with status code '{}', but error \
response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to verify Biome user: {}",
message
)))
}
})
}
fn list_users(&self) -> Result<Box<dyn Iterator<Item = Credentials>>, InternalError> {
let request = Client::new()
.get(&format!("{}/biome/users?limit={}", self.url, PAGING_LIMIT))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to list Biome users".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
let response_data: Box<dyn Iterator<Item = Credentials>> = Box::new(
res.json::<Vec<ClientCredentials>>()
.map(|entries| {
entries
.into_iter()
.map(Credentials::from)
.collect::<Vec<Credentials>>()
})
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response"
.into(),
)
})?
.into_iter(),
);
Ok(response_data)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome list users request failed with status code '{}', but \
error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to list Biome users: {}",
message
)))
}
})
}
fn get_user(&self, user_id: &str) -> Result<Option<Credentials>, InternalError> {
let request = Client::new()
.get(&format!("{}/biome/users/{}", self.url, user_id))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to fetch Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
res.json::<ClientCredentials>()
.map(Credentials::from)
.map(Some)
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})
} else if status == StatusCode::NOT_FOUND {
Ok(None)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome fetch user request failed with status code '{}', but \
error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to fetch Biome user: {}",
message
)))
}
})
}
fn update_user(
&self,
user_id: &str,
updated_user: UpdateUser,
) -> Result<Box<dyn Iterator<Item = Key>>, InternalError> {
let request = Client::new()
.put(&format!("{}/biome/users/{}", self.url, user_id))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?)
.json(&ClientUpdateUser::from(updated_user));
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to update Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
let response_data = res
.json::<ClientKeyListResponse>()
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})?
.data
.into_iter();
let keys: Box<dyn Iterator<Item = Key>> =
Box::new(response_data.map(Key::from));
Ok(keys)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome update user request failed with status code '{}', but \
error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to update Biome user: {}",
message
)))
}
})
}
fn delete_user(&self, user_id: &str) -> Result<(), InternalError> {
let request = Client::new()
.delete(&format!("{}/biome/users/{}", self.url, user_id))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to delete Biome user".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
Ok(())
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome delete user request failed with status code '{}', but error \
response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to delete Biome user: {}",
message
)))
}
})
}
fn list_profiles(&self) -> Result<Box<dyn Iterator<Item = Profile>>, InternalError> {
let request = Client::new()
.get(&format!(
"{}/biome/profiles?limit={}",
self.url, PAGING_LIMIT
))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to list Biome profiles".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
let response_data: Box<dyn Iterator<Item = Profile>> = Box::new(
res.json::<Vec<ClientProfile>>()
.map(|entries| {
entries
.into_iter()
.map(Profile::from)
.collect::<Vec<Profile>>()
})
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response"
.into(),
)
})?
.into_iter(),
);
Ok(response_data)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome list profiles request failed with status code '{}', \
but error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to list Biome profiles: {}",
message
)))
}
})
}
fn get_profile(&self, user_id: &str) -> Result<Option<Profile>, InternalError> {
let request = Client::new()
.get(&format!("{}/biome/profiles/{}", self.url, user_id))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to fetch Biome profile".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
res.json::<ClientProfile>().map(Profile::from).map(Some).map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})
} else if status == StatusCode::NOT_FOUND {
Ok(None)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome fetch profile request failed with status code '{}', but error \
response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to fetch Biome profile: {}",
message
)))
}
})
}
fn list_user_keys(&self) -> Result<Box<dyn Iterator<Item = Key>>, InternalError> {
let request = Client::new()
.get(&format!("{}/biome/keys?limit={}", self.url, PAGING_LIMIT))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to list Biome user's keys".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
let response_data = res
.json::<ClientKeyListResponse>()
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})?
.data
.into_iter();
let keys: Box<dyn Iterator<Item = Key>> =
Box::new(response_data.map(Key::from));
Ok(keys)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome list user's keys request failed with status code '{}', \
but error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to list Biome user's keys: {}",
message
)))
}
})
}
fn update_key(&self, public_key: &str, new_display_name: &str) -> Result<(), InternalError> {
let request = Client::new()
.patch(&format!("{}/biome/keys", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?)
.json(&json!({
"public_key": public_key,
"new_display_name": new_display_name,
}));
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to update Biome user's keys".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
Ok(())
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome update user's keys request failed with status code '{}', \
but error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to update Biome user's keys: {}",
message
)))
}
})
}
fn add_key(&self, user_id: &str, new_key: NewKey) -> Result<(), InternalError> {
let request = Client::new()
.post(&format!("{}/biome/keys", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?)
.json(&ClientKey::from((user_id.to_string(), new_key)));
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to add Biome user's keys".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
Ok(())
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome add user's keys request failed with status code '{}', \
but error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to add Biome user's keys: {}",
message
)))
}
})
}
fn replace_keys(&self, keys: Vec<NewKey>) -> Result<(), InternalError> {
let keys: Vec<ClientNewKey> = keys.into_iter().map(ClientNewKey::from).collect();
let request = Client::new()
.put(&format!("{}/biome/keys", self.url))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?)
.json(&keys);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to replace Biome user keys".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
Ok(())
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome replace user key request failed with status code '{}', but \
error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to replace user keys: {}",
message
)))
}
})
}
fn get_key(&self, public_key: &str) -> Result<Option<Key>, InternalError> {
let request = Client::new()
.get(&format!("{}/biome/keys/{}", self.url, public_key))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to get Biome user's keys".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
let response_data = res
.json::<ClientKeyResponse>()
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})?
.data;
Ok(Some(Key::from(response_data)))
} else if status == StatusCode::NOT_FOUND {
Ok(None)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome get user's keys request failed with status code '{}', \
but error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to get Biome user's keys: {}",
message
)))
}
})
}
fn delete_key(&self, public_key: &str) -> Result<Option<Key>, InternalError> {
let request = Client::new()
.delete(&format!("{}/biome/keys/{}", self.url, public_key))
.header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION)
.header("Authorization", &self.auth()?);
let response = request.send();
response
.map_err(|err| {
InternalError::from_source_with_message(
Box::new(err),
"Failed to delete Biome user's keys".to_string(),
)
})
.and_then(|res| {
let status = res.status();
if status.is_success() {
let response_data = res
.json::<ClientKeyResponse>()
.map_err(|_| {
InternalError::with_message(
"Request was successful, but received an invalid response".into(),
)
})?
.data;
Ok(Some(Key::from(response_data)))
} else if status == StatusCode::NOT_FOUND {
Ok(None)
} else {
let message = res
.json::<ServerError>()
.map_err(|err| {
InternalError::from_source_with_message(
err.into(),
format!(
"Biome delete user's keys request failed with status code '{}', \
but error response was not valid",
status
),
)
})?
.message;
Err(InternalError::with_message(format!(
"Failed to delete Biome user's keys: {}",
message
)))
}
})
}
}
#[derive(Debug, Deserialize)]
pub struct ClientCredentials {
pub user_id: String,
pub username: String,
}
impl From<ClientCredentials> for Credentials {
fn from(client_credentials: ClientCredentials) -> Self {
Credentials {
user_id: client_credentials.user_id,
username: client_credentials.username,
}
}
}
#[derive(Debug, Deserialize)]
pub struct ClientCredentialsResponse {
pub message: String,
pub data: ClientCredentials,
}
#[derive(Debug, Deserialize)]
pub struct ClientAuthorization {
pub user_id: String,
pub token: String,
pub refresh_token: String,
}
#[derive(Debug, Deserialize)]
pub struct ClientAccessToken {
pub token: String,
}
impl From<ClientAuthorization> for Authorization {
fn from(client_authorization: ClientAuthorization) -> Self {
Authorization {
user_id: client_authorization.user_id,
token: client_authorization.token,
refresh_token: client_authorization.refresh_token,
}
}
}
#[derive(Debug, Serialize)]
pub struct ClientUpdateUser {
pub username: String,
pub hashed_password: String,
pub new_password: Option<String>,
pub new_key_pairs: Vec<ClientNewKey>,
}
impl From<UpdateUser> for ClientUpdateUser {
fn from(update_user: UpdateUser) -> Self {
ClientUpdateUser {
username: update_user.username,
hashed_password: update_user.hashed_password,
new_password: update_user.new_password,
new_key_pairs: update_user
.new_key_pairs
.into_iter()
.map(ClientNewKey::from)
.collect(),
}
}
}
#[derive(Debug, Serialize)]
pub struct ClientNewKey {
pub public_key: String,
pub encrypted_private_key: String,
pub display_name: String,
}
impl From<NewKey> for ClientNewKey {
fn from(new_key: NewKey) -> Self {
ClientNewKey {
public_key: new_key.public_key,
encrypted_private_key: new_key.encrypted_private_key,
display_name: new_key.display_name,
}
}
}
#[derive(Debug, Deserialize)]
pub struct ClientKeyListResponse {
pub message: Option<String>,
pub data: Vec<ClientKey>,
}
#[derive(Debug, Deserialize)]
pub struct ClientKeyResponse {
pub message: Option<String>,
pub data: ClientKey,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClientKey {
pub display_name: String,
pub encrypted_private_key: String,
pub public_key: String,
pub user_id: String,
}
impl From<ClientKey> for Key {
fn from(client_key: ClientKey) -> Self {
Key {
public_key: client_key.public_key,
encrypted_private_key: client_key.encrypted_private_key,
display_name: client_key.display_name,
user_id: client_key.user_id,
}
}
}
impl From<(String, NewKey)> for ClientKey {
fn from((user_id, new_key): (String, NewKey)) -> Self {
ClientKey {
display_name: new_key.display_name,
encrypted_private_key: new_key.encrypted_private_key,
public_key: new_key.public_key,
user_id,
}
}
}
#[derive(Debug, Deserialize)]
pub struct ClientProfile {
pub user_id: String,
pub subject: String,
pub name: Option<String>,
}
impl From<ClientProfile> for Profile {
fn from(client_profile: ClientProfile) -> Self {
Profile {
user_id: client_profile.user_id,
subject: client_profile.subject,
name: client_profile.name,
}
}
}