Skip to content

Commit

Permalink
Oauth (#11)
Browse files Browse the repository at this point in the history
* OAUTH: authorization url

* OAUTH: authorization url

* OAUTH: request token + set_access_token

* OAUTH: request token + set_access_token

* OAUTH: dynamic function to store access token

* OAUTH: example and interface tuning

* OAUTH: example and interface tuning

* OAUTH: fixing tests

* CCG: Fix hardcoded bos_subject_type from env

* AUTH: example
  • Loading branch information
barduinor authored Aug 20, 2023
1 parent d762b6c commit 11c74ce
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 38 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Cargo.lock

.DS_Store
.access_token.json
*.cache.json
.env
*.env
# Added by cargo
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ rand = "0.8.5"
pretty_assertions = "1.3.0"
env_logger = { version = "0.10.0", default-features = false }
tokio = { version = "1.11.0", features = ["rt-multi-thread", "macros"] }
webbrowser = "0.8.9"
tiny_http = "0.12.0"
serde_qs = "0.12.0"

[[example]]
name = "auth_developer"
Expand Down
11 changes: 9 additions & 2 deletions examples/auth_ccg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ async fn main() -> Result<(), BoxAPIError> {

let client_id = env::var("CLIENT_ID").expect("CLIENT_ID must be set");
let client_secret = env::var("CLIENT_SECRET").expect("CLIENT_SECRET must be set");
let box_subject_type = SubjectType::Enterprise;
let box_subject_id = env::var("BOX_ENTERPRISE_ID").expect("BOX_ENTERPRISE_ID must be set");

let env_subject_type = env::var("BOX_SUBJECT_TYPE").expect("BOX_SUBJECT_TYPE must be set");
let box_subject_type = match env_subject_type.as_str() {
"user" => SubjectType::User,
"enterprise" => SubjectType::Enterprise,
_ => panic!("BOX_SUBJECT_TYPE must be either 'user' or 'enterprise'"),
};

let box_subject_id = env::var("BOX_SUBJECT_ID").expect("BOX_SUBJECT_ID must be set");

let config = Config::new();
let auth = CCGAuth::new(
Expand Down
46 changes: 46 additions & 0 deletions examples/auth_oauth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
mod oauth;

use rusty_box::{BoxAPIError, BoxClient, Config, OAuth};

use crate::oauth::{authorize_app, storage};
use std::env;

#[tokio::main]
async fn main() -> Result<(), BoxAPIError> {
dotenv::from_filename(".oauth.env").expect("Failed to read .env file");

let client_id = env::var("CLIENT_ID").expect("CLIENT_ID not set");
let client_secret = env::var("CLIENT_SECRET").expect("CLIENT_SECRET not set");
let redirect_uri = env::var("REDIRECT_URI").expect("REDIRECT_URI not set");

let config = Config::new();
let oauth = OAuth::new(
config,
client_id,
client_secret,
Some(storage::save_access_token),
);

// Load the OAuth token from the cache file
let oauth_json = oauth::storage::load_access_token();

let oauth = match oauth_json {
Ok(oauth_json) => {
println!("Cached token found, refreshing");
let oauth: OAuth =
serde_json::from_str(&oauth_json).expect("Failed to parse cached token");
oauth
}
Err(_) => {
println!("No cached token found, authorizing app");
authorize_app::authorize_app(oauth, Some(redirect_uri)).await?
}
};

let mut client = BoxClient::new(Box::new(oauth));

let me = rusty_box::users_api::me(&mut client, None).await;
println!("Me:\n{me:#?}\n");

Ok(())
}
32 changes: 32 additions & 0 deletions examples/oauth/authorize_app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use super::http_request_listener;
use http_request_listener::request_process;
use rusty_box::{AuthError, BoxAPIError, OAuth};

static HOSTNAME: &str = "127.0.0.1";
const PORT: i16 = 5000;

pub async fn authorize_app(
mut oauth: OAuth,
redirect_uri: Option<String>,
) -> Result<OAuth, BoxAPIError> {
let (authorization_url, state_out) = oauth.authorization_url(redirect_uri, None, None)?;

webbrowser::open(&authorization_url).expect("Failed to open browser");

let hostname_port = HOSTNAME.to_owned() + ":" + &PORT.to_string();
let server = tiny_http::Server::http(hostname_port).unwrap();
println!("Listening on {}", server.server_addr());

let (code, state_in) = match request_process(server) {
Ok((code, state)) => (code, state),
Err(e) => return Err(BoxAPIError::AuthError(AuthError::Generic(e))),
};
if state_in != state_out {
return Err(BoxAPIError::AuthError(AuthError::Generic(
"State mismatch".to_string(),
)));
}

oauth.request_access_token(code).await?;
Ok(oauth)
}
49 changes: 49 additions & 0 deletions examples/oauth/http_request_listener.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use url::Url;

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct UrlParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub error_description: Option<String>,
}

pub fn request_process(server: tiny_http::Server) -> Result<(String, String), String> {
match server.recv() {
Ok(rq) => {
let base_url = Url::parse("http://127.0.0.1").expect("Error parsing base URL");

let url = base_url
.join(rq.url())
.expect("Error parsing URL from request");

let query_params: UrlParams = match serde_qs::from_str(url.query().unwrap_or_default())
{
Ok(query_params) => query_params,
Err(_) => return Err("Error srializing url query".to_string()),
};

match query_params.code {
Some(code) => {
let response = tiny_http::Response::empty(200);
rq.respond(response)
.expect("Error sending response local server");
Ok((code, query_params.state.unwrap_or_default()))
}
None => Err(format!(
"{} {}",
query_params.error.unwrap_or_default(),
query_params.error_description.unwrap_or_default()
)),
}
}
Err(_) => Err("Error receiving request from local server".to_string()),
}
}
3 changes: 3 additions & 0 deletions examples/oauth/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod authorize_app;
pub mod http_request_listener;
pub mod storage;
7 changes: 7 additions & 0 deletions examples/oauth/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub fn save_access_token(json: String) {
std::fs::write(".token.cache.json", json).expect("Unable to save access token")
}

pub fn load_access_token() -> std::io::Result<String> {
std::fs::read_to_string(".token.cache.json")
}
9 changes: 7 additions & 2 deletions examples/users_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ async fn main() -> Result<(), BoxAPIError> {

let client_id = env::var("CLIENT_ID").expect("CLIENT_ID must be set");
let client_secret = env::var("CLIENT_SECRET").expect("CLIENT_SECRET must be set");
let box_subject_type = SubjectType::Enterprise;
let box_subject_id = env::var("BOX_ENTERPRISE_ID").expect("BOX_ENTERPRISE_ID must be set");
let env_subject_type = env::var("BOX_SUBJECT_TYPE").expect("BOX_SUBJECT_TYPE must be set");
let box_subject_type = match env_subject_type.as_str() {
"user" => SubjectType::User,
"enterprise" => SubjectType::Enterprise,
_ => panic!("BOX_SUBJECT_TYPE must be either 'user' or 'enterprise'"),
};
let box_subject_id = env::var("BOX_SUBJECT_ID").expect("BOX_SUBJECT_ID must be set");

let config = Config::new();
let auth = CCGAuth::new(
Expand Down
12 changes: 9 additions & 3 deletions src/auth/auth_ccg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl<'a> Auth<'a> for CCGAuth {
} else {
let access_token = match self.access_token.access_token.clone() {
Some(token) => token,
None => return Err(AuthError::Token("CCG token is not set".to_owned())),
None => return Err(AuthError::Generic("CCG token is not set".to_owned())),
};
Ok(access_token)
}
Expand Down Expand Up @@ -166,8 +166,14 @@ async fn test_ccg_request() {
let config = Config::new();
let client_id = env::var("CLIENT_ID").expect("CLIENT_ID must be set");
let client_secret = env::var("CLIENT_SECRET").expect("CLIENT_SECRET must be set");
let box_subject_type = SubjectType::Enterprise;
let box_subject_id = env::var("BOX_ENTERPRISE_ID").expect("BOX_ENTERPRISE_ID must be set");
let env_subject_type = env::var("BOX_SUBJECT_TYPE").expect("BOX_SUBJECT_TYPE must be set");
let box_subject_type = match env_subject_type.as_str() {
"user" => SubjectType::User,
"enterprise" => SubjectType::Enterprise,
_ => panic!("BOX_SUBJECT_TYPE must be either 'user' or 'enterprise'"),
};

let box_subject_id = env::var("BOX_SUBJECT_ID").expect("BOX_SUBJECT_ID must be set");

let mut auth = CCGAuth::new(
config,
Expand Down
4 changes: 2 additions & 2 deletions src/auth/auth_developer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ impl DevAuth {
impl<'a> Auth<'a> for DevAuth {
async fn access_token(&mut self) -> Result<String, AuthError> {
if self.is_expired() {
Err(AuthError::Token("Developer token has expired".to_owned()))
Err(AuthError::Generic("Developer token has expired".to_owned()))
} else {
let access_token = match self.access_token.access_token.clone() {
Some(token) => token,
None => return Err(AuthError::Token("Developer token is not set".to_owned())),
None => return Err(AuthError::Generic("Developer token is not set".to_owned())),
};
Ok(access_token)
}
Expand Down
8 changes: 4 additions & 4 deletions src/auth/auth_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub enum AuthError {
Network(reqwest::Error),
Serde(serde_json::Error),
Io(std::io::Error),
Token(String),
Generic(String),
ResponseError(AuthErrorResponse),
}

Expand All @@ -46,7 +46,7 @@ impl fmt::Display for AuthError {
AuthError::Network(e) => ("reqwest", e.to_string()),
AuthError::Serde(e) => ("serde", e.to_string()),
AuthError::Io(e) => ("IO", e.to_string()),
AuthError::Token(e) => ("Token", e.to_string()),
AuthError::Generic(e) => ("Token", e.to_string()),
AuthError::ResponseError(e) => ("API Error", e.to_string()),
};
write!(f, "error in {}: {}", module, e)
Expand All @@ -59,7 +59,7 @@ impl fmt::Debug for AuthError {
AuthError::Network(e) => ("reqwest", e.to_string()),
AuthError::Serde(e) => ("serde", e.to_string()),
AuthError::Io(e) => ("IO", e.to_string()),
AuthError::Token(e) => ("Token", e.to_string()),
AuthError::Generic(e) => ("Token", e.to_string()),
AuthError::ResponseError(e) => ("API Error", e.to_string()),
};
write!(f, "error in {}: {}", module, e)
Expand All @@ -86,7 +86,7 @@ impl From<std::io::Error> for AuthError {

impl From<String> for AuthError {
fn from(e: String) -> Self {
AuthError::Token(e)
AuthError::Generic(e)
}
}

Expand Down
Loading

0 comments on commit 11c74ce

Please sign in to comment.