mirror of
https://github.com/OMGeeky/google-apis-rs.git
synced 2026-02-23 15:49:49 +01:00
feat(CLI):write default and read app-secret
* if there is no secret file in json format, we write a default one that we will then read in a second iteration of the loop. That way, the user has an example of how such a file must look like. Next step is to cleanup the error type and implement the Error trait. Fixes #53
This commit is contained in:
@@ -1 +1,5 @@
|
||||
# HELLO ${id.upper()}
|
||||
# HELLO ${id.upper()}
|
||||
|
||||
## TODO: About authentication
|
||||
|
||||
Include information about application secret files, and how we automatically write a default one.
|
||||
@@ -49,41 +49,14 @@ Usage:
|
||||
output_used = True
|
||||
# handle output
|
||||
%>\
|
||||
${util.program_name()} [config] ${mangle_subcommand(resource)} ${mangle_subcommand(method)} ${' '.join(args)}
|
||||
${util.program_name()} [options] ${mangle_subcommand(resource)} ${mangle_subcommand(method)} ${' '.join(args)}
|
||||
% endfor # each method
|
||||
% endfor # end for each resource
|
||||
${util.program_name()} --help
|
||||
|
||||
% if param_used|struct_used|output_used or upload_protocols_used:
|
||||
Options:
|
||||
% if struct_used:
|
||||
-${STRUCT_FLAG} ${v_arg} set request structure field;
|
||||
${v_arg} supports cursor form 'field[${FIELD_SEP}subfield]...' to
|
||||
set the curor for upcoming values and supports the value form
|
||||
'field[${FIELD_SEP}subfield]...=value' to set an actual field.
|
||||
% endif
|
||||
% if upload_protocols_used:
|
||||
-${UPLOAD_FLAG} <mode> ${FILE_ARG} ${MIME_ARG}
|
||||
<mode> may be one of the following upload modes: ${put_and(sorted(upload_protocols_used))}
|
||||
${FILE_ARG} path to file to upload. It must be seekable.
|
||||
${MIME_ARG} the mime type, like 'application/octet-stream',
|
||||
which is the default
|
||||
% endif
|
||||
% if param_used:
|
||||
-${PARAM_FLAG} ${v_arg} set optional request parameter; ${v_arg} is of form 'name=value'
|
||||
% endif
|
||||
% if output_used:
|
||||
-${OUTPUT_FLAG} ${OUT_ARG}
|
||||
The `destination` to which to write the server result to.
|
||||
It will either be a json-encoded structure, or the
|
||||
media file you are downloading.
|
||||
`destination` may be '-' to indicate standard output, or
|
||||
a filepath that is to contain the received bytes.
|
||||
If unset, it defaults to standard output.
|
||||
% endif
|
||||
All documentation details can be found TODO: <URL to github.io docs here, see #51>
|
||||
|
||||
% endif # any special option is used
|
||||
Config:
|
||||
Configuration:
|
||||
% if supports_scopes(auth):
|
||||
--${SCOPE_FLAG} <url>
|
||||
Specify the authentication a method should be executed in. Each scope requires
|
||||
|
||||
@@ -7,26 +7,35 @@
|
||||
%>\
|
||||
<%def name="new(c)">\
|
||||
mod cmn;
|
||||
use cmn::{InvalidOptionsError, ArgumentError};
|
||||
use cmn::{InvalidOptionsError};
|
||||
|
||||
use std::fs;
|
||||
use oauth2::ApplicationSecret;
|
||||
|
||||
struct Engine {
|
||||
opts: Options,
|
||||
config_dir: String,
|
||||
secret: ApplicationSecret,
|
||||
}
|
||||
|
||||
|
||||
impl Engine {
|
||||
fn new(options: Options) -> Result<Engine, InvalidOptionsError> {
|
||||
{
|
||||
let (config_dir, secret) = {
|
||||
let config_dir = match cmn::assure_config_dir_exists(&options.flag_config_dir) {
|
||||
Err(e) => return Err(InvalidOptionsError::single(e, 3)),
|
||||
Ok(p) => p,
|
||||
};
|
||||
}
|
||||
|
||||
match cmn::application_secret_from_directory(&config_dir, "${util.program_name()}-secret.json") {
|
||||
Ok(secret) => (config_dir, secret),
|
||||
Err(e) => return Err(InvalidOptionsError::single(e, 4))
|
||||
}
|
||||
};
|
||||
|
||||
let mut engine = Engine {
|
||||
opts: options,
|
||||
config_dir: config_dir,
|
||||
secret: secret,
|
||||
};
|
||||
|
||||
Ok(engine)
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
use oauth2::ApplicationSecret;
|
||||
use oauth2::{ApplicationSecret, ConsoleApplicationSecret};
|
||||
use rustc_serialize::json;
|
||||
|
||||
use std::fs;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use std::io::{Write, Read};
|
||||
|
||||
use std::default::Default;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArgumentError {
|
||||
ConfigurationDirectoryInaccessible(io::Error),
|
||||
ConfigurationDirectoryInaccessible((String, io::Error)),
|
||||
ConfigurationDirectoryUnset,
|
||||
UsernameExpansionFailed(String),
|
||||
IOError((String, io::Error)),
|
||||
SecretDecoderError((String, json::DecoderError)),
|
||||
SecretFormatError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -48,13 +56,73 @@ pub fn assure_config_dir_exists(dir: &str) -> Result<String, ArgumentError> {
|
||||
|
||||
if let Err(err) = fs::create_dir(&expanded_config_dir) {
|
||||
if err.kind() != io::ErrorKind::AlreadyExists {
|
||||
return Err(ArgumentError::ConfigurationDirectoryInaccessible(err))
|
||||
return Err(ArgumentError::ConfigurationDirectoryInaccessible((expanded_config_dir, err)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(expanded_config_dir)
|
||||
}
|
||||
|
||||
// pub fn application_secret_from_directory(dir: String) -> Result<ApplicationSecret, io::Error> {
|
||||
pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Result<ApplicationSecret, ArgumentError> {
|
||||
let secret_path = Path::new(dir).join(secret_basename);
|
||||
let secret_str = || secret_path.as_path().to_str().unwrap().to_string();
|
||||
let secret_io_error = |io_err: io::Error| {
|
||||
Err(ArgumentError::IOError(
|
||||
(secret_str(), io_err)
|
||||
))
|
||||
};
|
||||
|
||||
// }
|
||||
for _ in 0..2 {
|
||||
match fs::File::open(&secret_path) {
|
||||
Err(mut e) => {
|
||||
if e.kind() == io::ErrorKind::NotFound {
|
||||
// Write our built-in one - user may adjust the written file at will
|
||||
let secret = ApplicationSecret {
|
||||
client_id: "14070749909-vgip2f1okm7bkvajhi9jugan6126io9v.apps.googleusercontent.com".to_string(),
|
||||
client_secret: "UqkDJd5RFwnHoiG5x5Rub8SI".to_string(),
|
||||
token_uri: "https://accounts.google.com/o/oauth2/token".to_string(),
|
||||
auth_uri: Default::default(),
|
||||
redirect_uris: Default::default(),
|
||||
client_email: None,
|
||||
auth_provider_x509_cert_url: None,
|
||||
client_x509_cert_url: Some("https://www.googleapis.com/oauth2/v1/certs".to_string())
|
||||
};
|
||||
|
||||
let app_secret = ConsoleApplicationSecret {
|
||||
installed: Some(secret),
|
||||
web: None,
|
||||
};
|
||||
|
||||
let json_enocded_secret = json::encode(&app_secret).unwrap();
|
||||
e = match fs::OpenOptions::new().create(true).write(true).open(&secret_path) {
|
||||
Err(cfe) => cfe,
|
||||
Ok(mut f) => {
|
||||
match f.write(json_enocded_secret.as_bytes()) {
|
||||
Err(io_err) => io_err,
|
||||
Ok(_) => continue,
|
||||
}
|
||||
}
|
||||
};
|
||||
// fall through to IO error handling
|
||||
}
|
||||
return secret_io_error(e)
|
||||
},
|
||||
Ok(mut f) => {
|
||||
let mut json_encoded_secret = String::new();
|
||||
if let Err(io_err) = f.read_to_string(&mut json_encoded_secret) {
|
||||
return secret_io_error(io_err)
|
||||
}
|
||||
match json::decode::<ConsoleApplicationSecret>(&json_encoded_secret) {
|
||||
Err(json_decode_error) => return Err(ArgumentError::SecretDecoderError(
|
||||
(secret_str(), json_decode_error)
|
||||
)),
|
||||
Ok(console_secret) => match console_secret.installed {
|
||||
Some(secret) => return Ok(secret),
|
||||
None => return Err(ArgumentError::SecretFormatError(secret_str()))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
Reference in New Issue
Block a user