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

Move OAuth code->token routines from SessionSrv to Builder-API #616

Merged
merged 1 commit into from
Jun 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/builder-api/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions components/builder-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ doc = false

[dependencies]
env_logger = "*"
hyper = "*"
iron = "*"
log = "*"
mount = "*"
Expand Down
43 changes: 43 additions & 0 deletions components/builder-api/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,24 @@ use std::result;

use hab_core;
use depot;
use hyper;
use protobuf;
use rustc_serialize::json;
use zmq;

use oauth;

#[derive(Debug)]
pub enum Error {
Auth(oauth::github::AuthErr),
BadPort(String),
Depot(depot::Error),
HabitatCore(hab_core::Error),
HyperError(hyper::error::Error),
HTTP(hyper::status::StatusCode),
IO(io::Error),
JsonDecode(json::DecoderError),
MissingScope(String),
Protobuf(protobuf::ProtobufError),
Zmq(zmq::Error),
}
Expand All @@ -29,10 +38,15 @@ pub type Result<T> = result::Result<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match *self {
Error::Auth(ref e) => format!("GitHub Authentication error, {}", e),
Error::BadPort(ref e) => format!("{} is an invalid port. Valid range 1-65535.", e),
Error::Depot(ref e) => format!("{}", e),
Error::HabitatCore(ref e) => format!("{}", e),
Error::HyperError(ref e) => format!("{}", e),
Error::HTTP(ref e) => format!("{}", e),
Error::IO(ref e) => format!("{}", e),
Error::JsonDecode(ref e) => format!("JSON decoding error, {}", e),
Error::MissingScope(ref e) => format!("Missing GitHub permission: {}", e),
Error::Protobuf(ref e) => format!("{}", e),
Error::Zmq(ref e) => format!("{}", e),
};
Expand All @@ -43,10 +57,15 @@ impl fmt::Display for Error {
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::Auth(_) => "GitHub authorization error.",
Error::BadPort(_) => "Received an invalid port or a number outside of the valid range.",
Error::Depot(ref err) => err.description(),
Error::HabitatCore(ref err) => err.description(),
Error::HyperError(ref err) => err.description(),
Error::HTTP(_) => "Non-200 HTTP response.",
Error::IO(ref err) => err.description(),
Error::JsonDecode(ref err) => err.description(),
Error::MissingScope(_) => "Missing GitHub authorization scope.",
Error::Protobuf(ref err) => err.description(),
Error::Zmq(ref err) => err.description(),
}
Expand All @@ -65,6 +84,30 @@ impl From<depot::Error> for Error {
}
}

impl From<hyper::error::Error> for Error {
fn from(err: hyper::error::Error) -> Self {
Error::HyperError(err)
}
}

impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::IO(err)
}
}

impl From<json::DecoderError> for Error {
fn from(err: json::DecoderError) -> Self {
Error::JsonDecode(err)
}
}

impl From<oauth::github::AuthErr> for Error {
fn from(err: oauth::github::AuthErr) -> Self {
Error::Auth(err)
}
}

impl From<protobuf::ProtobufError> for Error {
fn from(err: protobuf::ProtobufError) -> Error {
Error::Protobuf(err)
Expand Down
111 changes: 85 additions & 26 deletions components/builder-api/src/http/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ use iron::status;
use iron::headers::{Authorization, Bearer};
use protobuf;
use protocol::jobsrv::{Job, JobCreate, JobGet};
use protocol::sessionsrv::{Session, SessionCreate, SessionGet};
use protocol::sessionsrv::{OAuthProvider, Session, SessionCreate, SessionGet};
use protocol::vault::{Origin, OriginCreate, OriginGet};
use protocol::net::{Msg, NetError, ErrCode};
use protocol::net::{self, NetError, ErrCode};
use router::Router;
use rustc_serialize::json::{self, ToJson};
use zmq;

use error::Error;
use oauth::github;

pub fn authenticate(req: &mut Request,
ctx: &Arc<Mutex<zmq::Context>>)
-> result::Result<Session, Response> {
Expand All @@ -39,7 +42,10 @@ pub fn authenticate(req: &mut Request,
let session = protobuf::parse_from_bytes(rep.get_body()).unwrap();
Ok(session)
}
"NetError" => Err(render_net_error(&rep)),
"NetError" => {
let err: NetError = protobuf::parse_from_bytes(rep.get_body()).unwrap();
Err(render_net_error(&err))
}
_ => unreachable!("unexpected msg: {:?}", rep),
}
}
Expand All @@ -56,28 +62,70 @@ pub fn authenticate(req: &mut Request,
pub fn session_create(req: &mut Request, ctx: &Arc<Mutex<zmq::Context>>) -> IronResult<Response> {
let params = req.extensions.get::<Router>().unwrap();
let code = match params.find("code") {
Some(code) => code.to_string(),
Some(code) => code,
_ => return Ok(Response::with(status::BadRequest)),
};
let mut conn = Broker::connect(&ctx).unwrap();
let mut request = SessionCreate::new();
request.set_code(code.to_string());
conn.route(&request).unwrap();
match conn.recv() {
Ok(rep) => {
match rep.get_message_id() {
"Session" => {
let token: Session = protobuf::parse_from_bytes(rep.get_body()).unwrap();
let encoded = json::encode(&token.to_json()).unwrap();
Ok(Response::with((status::Ok, encoded)))
match github::authenticate(code) {
Ok(token) => {
match github::user(&token) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The matchers are certainly doing nice work here

Ok(user) => {
let mut conn = Broker::connect(&ctx).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the .unwrap() calls in this function acceptable. If I recall, the response thread will panic and kill it off, but does it return an error to the calling client or close the socket?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calls to unwrap() here are alright. Using an unwrap() in an Iron handler will result in a 500 response to the caller and print a panic to the console. This is the same thing as matching on Ok/Err and printing an error log to the console then returning the 500 status to the user.

I'm using unwrap() here instead of try! because failing to connect to the broker is a very bad error that the developer caused

let mut request = SessionCreate::new();
request.set_token(token);
request.set_extern_id(user.id);
request.set_email(user.email);
request.set_name(user.name);
request.set_provider(OAuthProvider::GitHub);
conn.route(&request).unwrap();
match conn.recv() {
Ok(rep) => {
match rep.get_message_id() {
"Session" => {
let token: Session = protobuf::parse_from_bytes(rep.get_body())
.unwrap();
let encoded = json::encode(&token.to_json()).unwrap();
Ok(Response::with((status::Ok, encoded)))
}
"NetError" => {
let err: NetError = protobuf::parse_from_bytes(rep.get_body())
.unwrap();
Ok(render_net_error(&err))
}
_ => unreachable!("unexpected msg: {:?}", rep),
}
}
Err(e) => {
error!("{:?}", e);
Ok(Response::with(status::ServiceUnavailable))
}
}
}
Err(e @ Error::JsonDecode(_)) => {
debug!("github user get, err={:?}", e);
let err = net::err(ErrCode::BAD_REMOTE_REPLY, "rg:auth:1");
Ok(render_net_error(&err))
}
Err(e) => {
debug!("github user get, err={:?}", e);
let err = net::err(ErrCode::BUG, "ss:auth:2");
Ok(render_net_error(&err))
}
"NetError" => Ok(render_net_error(&rep)),
_ => unreachable!("unexpected msg: {:?}", rep),
}
}
Err(Error::Auth(e)) => {
debug!("github authentication, err={:?}", e);
let err = net::err(ErrCode::REMOTE_REJECTED, e.error);
Ok(render_net_error(&err))
}
Err(e @ Error::JsonDecode(_)) => {
debug!("github authentication, err={:?}", e);
let err = net::err(ErrCode::BAD_REMOTE_REPLY, "ss:auth:1");
Ok(render_net_error(&err))
}
Err(e) => {
error!("{:?}", e);
Ok(Response::with(status::ServiceUnavailable))
error!("github authentication, err={:?}", e);
let err = net::err(ErrCode::BUG, "ss:auth:0");
Ok(render_net_error(&err))
}
}
}
Expand All @@ -100,7 +148,10 @@ pub fn origin_show(req: &mut Request, ctx: &Arc<Mutex<zmq::Context>>) -> IronRes
let encoded = json::encode(&origin.to_json()).unwrap();
Ok(Response::with((status::Ok, encoded)))
}
"NetError" => Ok(render_net_error(&rep)),
"NetError" => {
let err: NetError = protobuf::parse_from_bytes(rep.get_body()).unwrap();
Ok(render_net_error(&err))
}
_ => unreachable!("unexpected msg: {:?}", rep),
}
}
Expand Down Expand Up @@ -137,7 +188,10 @@ pub fn origin_create(req: &mut Request, ctx: &Arc<Mutex<zmq::Context>>) -> IronR
let encoded = json::encode(&origin.to_json()).unwrap();
Ok(Response::with((status::Created, encoded)))
}
"NetError" => Ok(render_net_error(&rep)),
"NetError" => {
let err: NetError = protobuf::parse_from_bytes(rep.get_body()).unwrap();
Ok(render_net_error(&err))
}
_ => unreachable!("unexpected msg: {:?}", rep),
}
}
Expand Down Expand Up @@ -165,7 +219,10 @@ pub fn job_create(req: &mut Request, ctx: &Arc<Mutex<zmq::Context>>) -> IronResu
let encoded = json::encode(&job.to_json()).unwrap();
Ok(Response::with((status::Created, encoded)))
}
"NetError" => Ok(render_net_error(&rep)),
"NetError" => {
let err: NetError = protobuf::parse_from_bytes(rep.get_body()).unwrap();
Ok(render_net_error(&err))
}
_ => unreachable!("unexpected msg: {:?}", rep),
}
}
Expand Down Expand Up @@ -199,7 +256,10 @@ pub fn job_show(req: &mut Request, ctx: &Arc<Mutex<zmq::Context>>) -> IronResult
let encoded = json::encode(&job.to_json()).unwrap();
Ok(Response::with((status::Ok, encoded)))
}
"NetError" => Ok(render_net_error(&rep)),
"NetError" => {
let err: NetError = protobuf::parse_from_bytes(rep.get_body()).unwrap();
Ok(render_net_error(&err))
}
_ => unreachable!("unexpected msg: {:?}", rep),
}
}
Expand All @@ -221,14 +281,13 @@ pub fn job_show(req: &mut Request, ctx: &Arc<Mutex<zmq::Context>>) -> IronResult
/// * The given encoded message was not a NetError
/// * The given messsage could not be decoded
/// * The NetError could not be encoded to JSON
fn render_net_error(msg: &Msg) -> Response {
assert_eq!(msg.get_message_id(), "NetError");
let err: NetError = protobuf::parse_from_bytes(msg.get_body()).unwrap();
fn render_net_error(err: &NetError) -> Response {
let encoded = json::encode(&err.to_json()).unwrap();
let status = match err.get_code() {
ErrCode::ENTITY_NOT_FOUND => status::NotFound,
ErrCode::NO_SHARD => status::ServiceUnavailable,
ErrCode::TIMEOUT => status::RequestTimeout,
ErrCode::BAD_REMOTE_REPLY => status::BadGateway,
_ => status::InternalServerError,
};
Response::with((status, encoded))
Expand Down
2 changes: 2 additions & 0 deletions components/builder-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern crate habitat_builder_protocol as protocol;
extern crate habitat_core as hab_core;
extern crate habitat_depot as depot;
extern crate habitat_net as hab_net;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
Expand All @@ -26,6 +27,7 @@ extern crate zmq;
pub mod config;
pub mod error;
pub mod http;
pub mod oauth;
pub mod server;

pub use self::config::Config;
Expand Down
39 changes: 36 additions & 3 deletions components/builder-dbcache/src/data_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,37 @@ pub trait BasicSet: Bucket {
}
}

/// A generic data set for reading and writing entities into the datastore with a time to live.
///
/// This is identical to `BasicSet` with the exception that entities expire.
pub trait ExpiringSet: Bucket {
/// Type of objects stored inside this data set.
type Record: Persistable;

/// Expiration time (in seconds) for any entities written to the set.
fn expiry() -> usize;

/// Retrieves a record from the data set with the given ID.
fn find(&self, id: &<Self::Record as Persistable>::Key) -> Result<Self::Record> {
let conn = try!(self.pool().get());
let bytes = try!(conn.get::<String, Vec<u8>>(Self::key(id)));
if bytes.is_empty() {
return Err(Error::EntityNotFound);
}
let value = parse_from_bytes(&bytes).unwrap();
Ok(value)
}

/// Write a new record to the data set with a TTL.
fn write(&self, record: &Self::Record) -> Result<()> {
let conn = try!(self.pool().get());
try!(conn.set_ex(Self::key(&record.primary_key()),
record.write_to_bytes().unwrap(),
Self::expiry()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, so you implement expiry() on your struct and hardcode a value, compute it, be random, whatever. Sweet

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly!

Ok(())
}
}

/// A specialized data set for reading and writing entities with a unique and sequential
/// identifier.
///
Expand Down Expand Up @@ -132,18 +163,20 @@ pub trait InstaSet: Bucket {

/// A data set for writing basic key/value indices.
pub trait IndexSet: Bucket {
/// Type of the lookup key
type Key: Clone + redis::FromRedisValue + redis::ToRedisArgs;
/// Type of the Value stored for each entry in the index.
type Value: redis::FromRedisValue + redis::ToRedisArgs;

/// Retrieves the value for the given ID.
fn find(&self, id: &str) -> Result<Self::Value> {
fn find(&self, id: &Self::Key) -> Result<Self::Value> {
let conn = try!(self.pool().get());
let value = try!(conn.hget(Self::prefix(), id));
let value = try!(conn.hget(Self::prefix(), id.clone()));
Ok(value)
}

/// Write a new index entry to the data set.
fn write(&self, id: &str, value: Self::Value) -> Result<()> {
fn write(&self, id: &Self::Key, value: Self::Value) -> Result<()> {
let conn = try!(self.pool().get());
try!(conn.hset(Self::prefix(), id.clone(), value));
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion components/builder-dbcache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ extern crate time;
pub mod data_store;
pub mod error;

pub use self::data_store::{ConnectionPool, Bucket, BasicSet, InstaSet, IndexSet};
pub use self::data_store::{ConnectionPool, Bucket, BasicSet, ExpiringSet, InstaSet, IndexSet};
pub use self::error::{Error, Result};
10 changes: 9 additions & 1 deletion components/builder-protocol/protocols/sessionsrv.proto
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package sessionsrv;

enum OAuthProvider {
GitHub = 0;
}

message Account {
required uint64 id = 1;
required string email = 2;
Expand All @@ -19,7 +23,11 @@ message SessionToken {
}

message SessionCreate {
required string code = 1;
required string token = 1;
required uint64 extern_id = 2;
required string email = 3;
required string name = 4;
required OAuthProvider provider = 5;
}

message SessionGet {
Expand Down
Loading