Skip to content

Commit

Permalink
Move OAuth code->token routines from SessionSrv to Builder-API
Browse files Browse the repository at this point in the history
* Add `ExpiringSet` to dbcache crate. This is identical to the `BasicSet` except entities will
  expire from the set after an expiration time defined by a static trait function
* `IndexSet` can now set the type of Key along with the type of the Value it stores at that key
* Add GitHub -> Account directory lookup index to SessionSrv. This is used to map external IDs
  to our internal ID format
* Fixes routing of SessionCreate messages. All messages will be routed by their external ID

Signed-off-by: Jamie Winsor <[email protected]>

Pull request: #616
Approved by: reset
  • Loading branch information
reset authored and jtimberman committed Jun 12, 2016
1 parent 915fc73 commit f1eebca
Show file tree
Hide file tree
Showing 20 changed files with 632 additions and 224 deletions.
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) {
Ok(user) => {
let mut conn = Broker::connect(&ctx).unwrap();
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
File renamed without changes.
File renamed without changes.
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()));
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

0 comments on commit f1eebca

Please sign in to comment.