diff --git a/src/mako/cli/README.md.mako b/src/mako/cli/README.md.mako index 2f6d17826e9..6684015cb04 100644 --- a/src/mako/cli/README.md.mako +++ b/src/mako/cli/README.md.mako @@ -1 +1,5 @@ -# HELLO ${id.upper()} \ No newline at end of file +# HELLO ${id.upper()} + +## TODO: About authentication + +Include information about application secret files, and how we automatically write a default one. \ No newline at end of file diff --git a/src/mako/cli/lib/docopt.mako b/src/mako/cli/lib/docopt.mako index 5bf9a7804f8..7ce6b4234d1 100644 --- a/src/mako/cli/lib/docopt.mako +++ b/src/mako/cli/lib/docopt.mako @@ -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} ${FILE_ARG} ${MIME_ARG} - 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: -% endif # any special option is used -Config: +Configuration: % if supports_scopes(auth): --${SCOPE_FLAG} Specify the authentication a method should be executed in. Each scope requires diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index 8753a26be47..8d782f10f0a 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -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 { - { + 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) diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index 60d37065a2f..c9f90339985 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -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 { 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 { +pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Result { + 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()) + }; -// } \ No newline at end of file + 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::(&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!(); +} \ No newline at end of file