Skip to content

Commit

Permalink
feat(flows): Implement flow for installed apps
Browse files Browse the repository at this point in the history
The "Installed App Flow" requires user interaction; we first generate a
URL that the user has to navigate to. Then, the user either pastes a
displayed code into the app being authorized, or the OAuth provider
redirects the user's browser to a webserver that is running on
localhost. This webserver is provided by the library and the flow should
work automatically. Extensive documentation can be found here:
https://developers.google.com/identity/protocols/OAuth2InstalledApp
An example for the InstalledFlow with the Drive API is here:
https://gist.github.com/dermesser/8c915ec4c88ee8e8927e7d40b276ca52
  • Loading branch information
dermesser committed Apr 16, 2016
1 parent 2aa95c0 commit 7735588
Show file tree
Hide file tree
Showing 4 changed files with 424 additions and 6 deletions.
13 changes: 12 additions & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,26 @@ impl Token {
/// All known authentication types, for suitable constants
#[derive(Clone, Copy)]
pub enum FlowType {
/// [device authentication](https://developers.google.com/youtube/v3/guides/authentication#devices)
/// [device authentication](https://developers.google.com/youtube/v3/guides/authentication#devices). Only works
/// for certain scopes.
Device,
/// [installed app flow](https://developers.google.com/identity/protocols/OAuth2InstalledApp). Required
/// for Drive, Calendar, Gmail...; Requires user to paste a code from the browser.
InstalledInteractive,
/// Same as InstalledInteractive, but uses a redirect: The OAuth provider redirects the user's
/// browser to a web server that is running on localhost. This may not work as well with the
/// Windows Firewall, but is more comfortable otherwise. The integer describes which port to
/// bind to (default: 8080)
InstalledRedirect(u32),
}

impl AsRef<str> for FlowType {
/// Converts itself into a URL string
fn as_ref(&self) -> &'static str {
match *self {
FlowType::Device => "https://accounts.google.com/o/oauth2/device/code",
FlowType::InstalledInteractive => "https://accounts.google.com/o/oauth2/v2/auth",
FlowType::InstalledRedirect(_) => "https://accounts.google.com/o/oauth2/v2/auth",
}
}
}
Expand Down
55 changes: 53 additions & 2 deletions src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use std::cmp::min;
use std::error::Error;
use std::fmt;
use std::convert::From;
use std::io;

use common::{Token, FlowType, ApplicationSecret};
use device::{PollInformation, RequestError, DeviceFlow, PollError};
use installed::{InstalledFlow, InstalledFlowReturnMethod};
use refresh::{RefreshResult, RefreshFlow};
use chrono::{DateTime, UTC, Local};
use std::time::Duration;
Expand Down Expand Up @@ -189,6 +191,27 @@ impl<D, S, C> Authenticator<D, S, C>
}
}


fn do_installed_flow(&mut self, scopes: &Vec<&str>) -> Result<Token, Box<Error>> {
let installed_type;

match self.flow_type {
FlowType::InstalledInteractive => {
installed_type = Some(InstalledFlowReturnMethod::Interactive)
}
FlowType::InstalledRedirect(port) => {
installed_type = Some(InstalledFlowReturnMethod::HTTPRedirect(port))
}
_ => installed_type = None,
}

let mut flow = InstalledFlow::new(self.client.borrow_mut(), installed_type);
flow.obtain_token(&mut self.delegate,
&self.secret.client_id,
&self.secret.client_secret,
scopes.iter())
}

fn retrieve_device_token(&mut self, scopes: &Vec<&str>) -> Result<Token, Box<Error>> {
let mut flow = DeviceFlow::new(self.client.borrow_mut());

Expand Down Expand Up @@ -448,9 +471,37 @@ pub trait AuthenticatorDelegate {
/// * Will only be called if the Authenticator's flow_type is `FlowType::Device`.
fn present_user_code(&mut self, pi: &PollInformation) {
println!("Please enter {} at {} and grant access to this application",
pi.user_code, pi.verification_url);
pi.user_code,
pi.verification_url);
println!("Do not close this application until you either denied or granted access.");
println!("You have time until {}.", pi.expires_at.with_timezone(&Local));
println!("You have time until {}.",
pi.expires_at.with_timezone(&Local));
}

/// Only method currently used by the InstalledFlow.
/// We need the user to navigate to a URL using their browser and potentially paste back a code
/// (or maybe not). Whether they have to enter a code depends on the InstalledFlowReturnMethod
/// used.
fn present_user_url(&mut self, url: &String, need_code: bool) -> Option<String> {
if need_code {
println!("Please direct your browser to {}, follow the instructions and enter the \
code displayed here: ",
url);

let mut code = String::new();
let _ = io::stdin().read_line(&mut code);

if !code.is_empty() {
Some(code)
} else {
None
}
} else {
println!("Please direct your browser to {} and follow the instructions displayed \
there.",
url);
None
}
}
}

Expand Down
Loading

0 comments on commit 7735588

Please sign in to comment.