Skip to content

Commit

Permalink
feat(CLI):write default and read app-secret
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Byron committed Apr 13, 2015
1 parent 5799d44 commit 4548644
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 40 deletions.
6 changes: 5 additions & 1 deletion src/mako/cli/README.md.mako
Original file line number Diff line number Diff line change
@@ -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.
33 changes: 3 additions & 30 deletions src/mako/cli/lib/docopt.mako
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions src/mako/cli/lib/engine.mako
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
78 changes: 73 additions & 5 deletions src/rust/cli/cmn.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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!();
}

0 comments on commit 4548644

Please sign in to comment.