diff --git a/sample_config.json b/sample_config.json index 7b9a2d4..f122baf 100644 --- a/sample_config.json +++ b/sample_config.json @@ -2,5 +2,6 @@ "api_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "base_api": "/api/v1", "base_url": "https://www.example.com", - "username": "jimmyjoebob" -} \ No newline at end of file + "username": "jimmyjoebob", + "password": "ihazcheeseburgerz" +} diff --git a/src/arg.rs b/src/arg.rs index dddb2d4..bf35e71 100644 --- a/src/arg.rs +++ b/src/arg.rs @@ -18,7 +18,7 @@ pub fn get_args() -> ArgMatches<'static> { .arg(Arg::with_name("search") .short("s") .long("search") - .value_name("REPO") + .value_names(&["REPO"]) .help("Search repositories for a user") ) .arg(Arg::with_name("list") @@ -68,4 +68,4 @@ pub fn get_args() -> ArgMatches<'static> { .get_matches(); matches -} \ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs index 947a5ad..024656c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,14 +1,15 @@ use std::env; use config::File; -use serde::{Deserialize}; +use serde::Deserialize; -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Configuration { - pub api_token: String, + pub api_token: Option, pub base_api: String, pub base_url: String, - pub username: Option + pub username: Option, + pub password: Option } impl Configuration { @@ -31,4 +32,4 @@ impl Configuration { config } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index c72cea3..9823d49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,21 +7,25 @@ mod arg; mod config; mod issue; mod repo; +mod request; -use clap::{ArgMatches}; +use clap::ArgMatches; fn main() { - let matches: ArgMatches = arg::get_args(); - let config = crate::config::Configuration::new(); + let mut matches: ArgMatches = arg::get_args(); + let mut config = crate::config::Configuration::new(); + + let auth = request::Authentication::new(&config); + let request = auth.request_chooser(config.clone(), matches); - match matches.subcommand() { + match request.arg_value.subcommand() { ("", None) => println!("No subcommand was given!"), ("repo", Some(repo_matches)) => { let repo = repo::Repository::new(); // TODO: match expression should be here if repo_matches.is_present("create") { - repo.create_repo(&config, repo_matches); + repo.create_repo(&request); } if repo_matches.is_present("delete") { @@ -50,4 +54,5 @@ fn main() { }, _ => println!("Huh?") } -} \ No newline at end of file +} + diff --git a/src/repo.rs b/src/repo.rs index 4e03b73..1a41b01 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -6,6 +6,7 @@ use reqwest::{StatusCode, Client}; use serde_derive::{Serialize, Deserialize}; use crate::config::Configuration; +use crate::request::Request; pub struct Repository; @@ -102,14 +103,17 @@ impl Repository { Repository {} } - pub fn create_repo(&self, config: &Configuration, arg: &ArgMatches) { - let client = Client::new(); - let arg_value = arg.value_of("create").unwrap(); + pub fn create_repo(&self, request: &Request) { + let client = &request.client; + let arg_value = request.arg_value.subcommand().1.unwrap().value_of("create").unwrap(); let mut map: HashMap<&str, &str> = HashMap::new(); - let url = format!("{base_url}{base_api}/user/repos?token={api_token}", - base_url = config.base_url, - base_api = config.base_api, - api_token = config.api_token); + let url = format!("{request}/user/repos?token={api_token}", + request = request.url.as_ref().unwrap(), + api_token = request + .authentication + .credentials + .1.as_ref() + .unwrap()); map.insert("name", arg_value); map.insert("readme", arg_value); @@ -147,7 +151,7 @@ impl Repository { base_api = config.base_api, username = arg_iter[0], repo_name = arg_iter[1], - api_token = config.api_token); + api_token = config.api_token.as_ref().unwrap()); let response = client.delete(&url) .send(); @@ -182,7 +186,7 @@ impl Repository { base_api = config.base_api, owner = arg_item[0], repo = arg_item[1], - api_token = config.api_token); + api_token = config.api_token.as_ref().unwrap()); map.insert("name", user.as_str()); @@ -216,7 +220,7 @@ impl Repository { base_url = config.base_url, base_api = config.base_api, query = arg_value, - api_token = config.api_token); + api_token = config.api_token.as_ref().unwrap()); let response = client.get(url.as_str()) .send(); @@ -255,7 +259,7 @@ impl Repository { let url = format!("{base_url}{base_api}/repos/search?token={api_token}", base_url = config.base_url, base_api = config.base_api, - api_token = config.api_token); + api_token = config.api_token.as_ref().unwrap()); let response = client.get(url.as_str()) .send(); diff --git a/src/request.rs b/src/request.rs index d9038b8..2eb89cd 100644 --- a/src/request.rs +++ b/src/request.rs @@ -7,28 +7,55 @@ use crate::config::Configuration; pub struct Request<'a> { pub client: Client, - pub arg_value: Vec<&'a str>, + pub arg_value: ArgMatches<'a>, pub map: HashMap, pub url: Option, + pub authentication: Authentication, } pub struct Authentication { pub basic: bool, pub api_token: bool, + pub credentials: (Option, Option) } -impl Request<'static> { - fn new() -> Request<'static> { +impl<'a> Request<'a> { + /// Public constructor for a request with a simple username and password + pub fn with_basic_request( + config: Configuration, + arg: ArgMatches, + auth: Authentication + ) -> Request { Request { client: Client::new(), - arg_value: Vec::new(), + arg_value: arg, map: HashMap::new(), - url: None + url: Some(format!("{:?}{:?}", config.base_url, config.base_api)), + authentication: auth, + } + } + + /// Public constructor for a request with an API token + pub fn with_api_request( + config: Configuration, + arg: ArgMatches, + auth: Authentication + ) -> Request { + Request { + client: Client::new(), + arg_value: arg, + map: HashMap::new(), + url: Some(format!("{:?}{:?}", config.base_url, config.base_api)), + authentication: auth, } } } -impl Authentication { +impl Authentication{ + /// Public constructor for getting authentication, provided by the configuration + /// file. The most secure methods are checked first, filtering down to the least + /// secure methods. Currently, only two methods are supported: API token, and + /// username/password combo. pub fn new(config: &Configuration) -> Authentication { let basic_auth: String; let api_auth: String; @@ -37,28 +64,67 @@ impl Authentication { // this is horror code, I know it // match the damn thing // someone is going to take this and put it in r/badcode lol + basic_auth = config + .password + .as_ref() + .unwrap() + .to_string(); + + api_auth = config + .api_token + .as_ref() + .unwrap() + .to_string(); + + if api_auth.is_empty() { + if !(basic_auth.is_empty()) { + Authentication::with_basic(config.username.as_ref().unwrap().to_string(), basic_auth) + } else { + panic!("Must have some form of authentication! Exiting..."); + } + } else { + Authentication::with_api_token(api_auth) + } - match config { - _ => panic!() - } } - fn with_basic() -> Authentication { + /// Private constructor once the public constructor figures out what kind of authentication + /// is being used. This constructor uses the username/password combo, a less secure of + /// authenticating that the API token. + fn with_basic(user: String, pass: String) -> Authentication { Authentication { basic: true, - api_token: false + api_token: false, + credentials: (Some(user), Some(pass)) } } - fn with_api_token() -> Authentication { + /// Private constructor once the public constructor figures out what kind of authentication + /// is being used. This constructor uses the API token, a more secure way of authenticating + /// instead of using the basic username and password. + fn with_api_token(api_token: String) -> Authentication { Authentication { basic: false, - api_token: true + api_token: true, + credentials: (Some(api_token), None) } } - // This method requires the instanciated config, an optional instanciated args, and instanciated auth - pub fn request_chooser(&self, config: &Configuration, arg: Option<&ArgMatches>, auth: &Authentication, arg_string: &str) { - + /// Public method that based on the what kind of authentication is being used, it can + /// determine what kind of requesting method is going to be used. See the Request + /// structure for more details. + pub fn request_chooser( + self, + config: Configuration, + arg: ArgMatches<'static>, + ) -> Request { + if let true = self.api_token { + Request::with_api_request(config, arg.to_owned(), self) + } else { + match self.basic { + true => Request::with_basic_request(config, arg.to_owned(), self), + false => panic!(), + } + } } -} \ No newline at end of file +}