Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stronger typing using the NewType pattern #38

Closed
wants to merge 11 commits into from
Binary file added .DS_Store
Binary file not shown.
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ description = "Bindings for exchanging OAuth 2 tokens"
repository = "https://github.com/alexcrichton/oauth2-rs"

[dependencies]
base64 = "0.9"
curl = "0.4.0"
failure = "0.1"
failure_derive = "0.1"
log = "0.3"
rand = "0.4"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
url = "1.0"

[dev-dependencies]
base64 = "0.9"
mockito = "0.8.2"
rand = "0.4"
155 changes: 99 additions & 56 deletions examples/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,93 +18,136 @@ extern crate oauth2;
extern crate rand;
extern crate url;

use oauth2::prelude::*;
use oauth2::{
AuthorizationCode,
AuthUrl,
ClientId,
ClientSecret,
CsrfToken,
RedirectUrl,
Scope,
Token,
TokenUrl,
};
use oauth2::basic::BasicClient;
use rand::{thread_rng, Rng};
use std::env;
use std::net::TcpListener;
use std::io::{BufRead, BufReader, Write};
use url::Url;

fn main() {
let github_client_id = env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable.");
let github_client_secret = env::var("GITHUB_CLIENT_SECRET").expect("Missing the GITHUB_CLIENT_SECRET environment variable.");
let auth_url = "https://github.com/login/oauth/authorize";
let token_url = "https://github.com/login/oauth/access_token";
let github_client_id =
ClientId::new(
env::var("GITHUB_CLIENT_ID")
.expect("Missing the GITHUB_CLIENT_ID environment variable.")
);
let github_client_secret =
ClientSecret::new(
env::var("GITHUB_CLIENT_SECRET")
.expect("Missing the GITHUB_CLIENT_SECRET environment variable.")
);
let auth_url =
AuthUrl::new(
Url::parse("https://github.com/login/oauth/authorize")
.expect("Invalid authorization endpoint URL")
);
let token_url =
TokenUrl::new(
Url::parse("https://github.com/login/oauth/access_token")
.expect("Invalid token endpoint URL")
);

// Set up the config for the Github OAuth2 process.
let client =
BasicClient::new(github_client_id, Some(github_client_secret), auth_url, token_url)
.expect("failed to create client")

// This example is requesting access to the user's public repos and email.
.add_scope("public_repo")
.add_scope("user:email")
.add_scope(Scope::new("public_repo".to_string()))
.add_scope(Scope::new("user:email".to_string()))

// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_url("http://localhost:8080");

let mut rng = thread_rng();
// Generate a 128-bit random string for CSRF protection (each time!).
let random_bytes: Vec<u8> = (0..16).map(|_| rng.gen::<u8>()).collect();
let csrf_state = base64::encode(&random_bytes);
.set_redirect_url(
RedirectUrl::new(
Url::parse("http://localhost:8080")
.expect("Invalid redirect URL")
)
);

// Generate the authorization URL to which we'll redirect the user.
let authorize_url = client.authorize_url(csrf_state.clone());
let (authorize_url, csrf_state) = client.authorize_url(CsrfToken::new_random);

println!("Open this URL in your browser:\n{}\n", authorize_url.to_string());

// These variables will store the code & state retrieved during the authorization process.
let mut code = String::new();
let mut state = String::new();

// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
{
let mut reader = BufReader::new(&stream);
if let Ok(mut stream) = stream {
let code;
let state;
{
let mut reader = BufReader::new(&stream);

let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();

let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();

let code_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "code"
}).unwrap();
let code_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "code"
}).unwrap();

let (_, value) = code_pair;
code = value.into_owned();
let (_, value) = code_pair;
code = AuthorizationCode::new(value.into_owned());

let state_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "state"
}).unwrap();
let state_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "state"
}).unwrap();

let (_, value) = state_pair;
state = value.into_owned();
}

let message = "Go back to your terminal :)";
let response = format!("HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", message.len(), message);
stream.write_all(response.as_bytes()).unwrap();
let (_, value) = state_pair;
state = CsrfToken::new(value.into_owned());
}

// The server will terminate itself after collecting the first code.
break;
let message = "Go back to your terminal :)";
let response =
format!("HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", message.len(), message);
stream.write_all(response.as_bytes()).unwrap();

println!("Github returned the following code:\n{}\n", code.secret());
println!(
"Github returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);

// Exchange the code with a token.
let token_res = client.exchange_code(code);

println!("Github returned the following token:\n{:?}\n", token_res);

if let Ok(token) = token_res {
// NB: Github returns a single comma-separated "scope" parameter instead of multiple
// space-separated scopes. Github-specific clients can parse this scope into
// multiple scopes by splitting at the commas. Note that it's not safe for the
// library to do this by default because RFC 6749 allows scopes to contain commas.
let scopes =
if let Some(scopes_vec) = token.scopes() {
scopes_vec
.iter()
.map(|comma_separated| comma_separated.split(","))
.flat_map(|inner_scopes| inner_scopes)
.collect::<Vec<_>>()
} else {
Vec::new()
};
println!("Github returned the following scopes:\n{:?}\n", scopes);
}
Err(_) => {},

// The server will terminate itself after collecting the first code.
break;
}
};

println!("Github returned the following code:\n{}\n", code);
println!("Github returned the following state:\n{} (expected `{}`)\n", state, csrf_state);

// Exchange the code with a token.
let token = client.exchange_code(code);

println!("Github returned the following token:\n{:?}\n", token);
}
133 changes: 79 additions & 54 deletions examples/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,92 +18,117 @@ extern crate oauth2;
extern crate rand;
extern crate url;

use oauth2::prelude::*;
use oauth2::{
AuthorizationCode,
AuthUrl,
ClientId,
ClientSecret,
CsrfToken,
RedirectUrl,
Scope,
TokenUrl,
};
use oauth2::basic::BasicClient;
use rand::{thread_rng, Rng};
use std::env;
use std::net::TcpListener;
use std::io::{BufRead, BufReader, Write};
use url::Url;

fn main() {
let google_client_id = env::var("GOOGLE_CLIENT_ID").expect("Missing the GOOGLE_CLIENT_ID environment variable.");
let google_client_secret = env::var("GOOGLE_CLIENT_SECRET").expect("Missing the GOOGLE_CLIENT_SECRET environment variable.");
let auth_url = "https://accounts.google.com/o/oauth2/v2/auth";
let token_url = "https://www.googleapis.com/oauth2/v3/token";
let google_client_id =
ClientId::new(
env::var("GOOGLE_CLIENT_ID")
.expect("Missing the GOOGLE_CLIENT_ID environment variable.")
);
let google_client_secret =
ClientSecret::new(
env::var("GOOGLE_CLIENT_SECRET")
.expect("Missing the GOOGLE_CLIENT_SECRET environment variable.")
);
let auth_url =
AuthUrl::new(
Url::parse("https://accounts.google.com/o/oauth2/v2/auth")
.expect("Invalid authorization endpoint URL")
);
let token_url =
TokenUrl::new(
Url::parse("https://www.googleapis.com/oauth2/v3/token")
.expect("Invalid token endpoint URL")
);

// Set up the config for the Google OAuth2 process.
let client =
BasicClient::new(google_client_id, Some(google_client_secret), auth_url, token_url)
.expect("failed to create client")
// This example is requesting access to the "calendar" features and the user's profile.
.add_scope("https://www.googleapis.com/auth/calendar")
.add_scope("https://www.googleapis.com/auth/plus.me")
.add_scope(Scope::new("https://www.googleapis.com/auth/calendar".to_string()))
.add_scope(Scope::new("https://www.googleapis.com/auth/plus.me".to_string()))

// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_url("http://localhost:8080");

let mut rng = thread_rng();
// Generate a 128-bit random string for CSRF protection (each time!).
let random_bytes: Vec<u8> = (0..16).map(|_| rng.gen::<u8>()).collect();
let csrf_state = base64::encode(&random_bytes);
.set_redirect_url(
RedirectUrl::new(
Url::parse("http://localhost:8080")
.expect("Invalid redirect URL")
)
);

// Generate the authorization URL to which we'll redirect the user.
let authorize_url = client.authorize_url(csrf_state.clone());
let (authorize_url, csrf_state) = client.authorize_url(CsrfToken::new_random);

println!("Open this URL in your browser:\n{}\n", authorize_url.to_string());

// These variables will store the code & state retrieved during the authorization process.
let mut code = String::new();
let mut state = String::new();

// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
{
let mut reader = BufReader::new(&stream);
if let Ok(mut stream) = stream {
let code;
let state;
{
let mut reader = BufReader::new(&stream);

let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();

let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();

let code_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "code"
}).unwrap();
let code_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "code"
}).unwrap();

let (_, value) = code_pair;
code = value.into_owned();
let (_, value) = code_pair;
code = AuthorizationCode::new(value.into_owned());

let state_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "state"
}).unwrap();
let state_pair = url.query_pairs().find(|pair| {
let &(ref key, _) = pair;
key == "state"
}).unwrap();

let (_, value) = state_pair;
state = value.into_owned();
}
let (_, value) = state_pair;
state = CsrfToken::new(value.into_owned());
}

let message = "Go back to your terminal :)";
let response = format!("HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", message.len(), message);
stream.write_all(response.as_bytes()).unwrap();
let message = "Go back to your terminal :)";
let response =
format!("HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", message.len(), message);
stream.write_all(response.as_bytes()).unwrap();

// The server will terminate itself after collecting the first code.
break;
}
Err(_) => {},
}
};
println!("Google returned the following code:\n{}\n", code.secret());
println!(
"Google returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);

println!("Google returned the following code:\n{}\n", code);
println!("Google returned the following state:\n{} (expected `{}`)\n", state, csrf_state);
// Exchange the code with a token.
let token = client.exchange_code(code);

// Exchange the code with a token.
let token = client.exchange_code(code);
println!("Google returned the following token:\n{:?}\n", token);

println!("Google returned the following token:\n{:?}\n", token);
// The server will terminate itself after collecting the first code.
break;
}
};
}
Loading