diff options
author | Shaun Taheri <shaun@advancedtelematic.com> | 2016-09-26 16:18:21 +0200 |
---|---|---|
committer | Shaun Taheri <shaun@advancedtelematic.com> | 2016-09-29 16:28:21 +0200 |
commit | b4d263c28fbc408d6dc2a437bd4a4affd5b6072e (patch) | |
tree | 8d549735db8e9cd8c221999eb11d69b1cded38c8 /src | |
parent | 484e98981f5ddbf61a9e4ca6190c9f2c2fcdec4c (diff) | |
download | rvi_sota_client-b4d263c28fbc408d6dc2a437bd4a4affd5b6072e.tar.gz |
Return the HTTP Body when available
Diffstat (limited to 'src')
-rw-r--r-- | src/datatype/auth.rs | 29 | ||||
-rw-r--r-- | src/datatype/command.rs | 16 | ||||
-rw-r--r-- | src/datatype/error.rs | 9 | ||||
-rw-r--r-- | src/datatype/json_rpc.rs | 10 | ||||
-rw-r--r-- | src/datatype/mod.rs | 2 | ||||
-rw-r--r-- | src/gateway/http.rs | 10 | ||||
-rw-r--r-- | src/http/auth_client.rs | 162 | ||||
-rw-r--r-- | src/http/http_client.rs | 44 | ||||
-rw-r--r-- | src/http/mod.rs | 2 | ||||
-rw-r--r-- | src/http/test_client.rs | 10 | ||||
-rw-r--r-- | src/interpreter.rs | 20 | ||||
-rw-r--r-- | src/oauth2.rs | 11 | ||||
-rw-r--r-- | src/package_manager/rpm.rs | 2 | ||||
-rw-r--r-- | src/sota.rs | 49 |
14 files changed, 223 insertions, 153 deletions
diff --git a/src/datatype/auth.rs b/src/datatype/auth.rs index cbfd097..83c872a 100644 --- a/src/datatype/auth.rs +++ b/src/datatype/auth.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; #[derive(Clone, Debug)] pub enum Auth { None, - Credentials(ClientId, ClientSecret), + Credentials(ClientCredentials), Token(AccessToken), } @@ -16,8 +16,15 @@ impl<'a> Into<Cow<'a, Auth>> for Auth { } -/// For storage of the returned access token data following a successful -/// authentication. +/// Encapsulates the client id and secret used during authentication. +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientCredentials { + pub client_id: String, + pub client_secret: String, +} + + +/// Stores the returned access token data following a successful authentication. #[derive(RustcDecodable, Debug, PartialEq, Clone, Default)] pub struct AccessToken { pub access_token: String, @@ -31,19 +38,3 @@ impl<'a> Into<Cow<'a, AccessToken>> for AccessToken { Cow::Owned(self) } } - - -/// Encapsulates a `String` type for use in `Auth::Credentials` -#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientId(pub String); - -/// Encapsulates a `String` type for use in `Auth::Credentials` -#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientSecret(pub String); - -/// Encapsulates the client id and secret used during authentication. -#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientCredentials { - pub client_id: ClientId, - pub client_secret: ClientSecret, -} diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 71567da..c88e8d5 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -3,8 +3,8 @@ use std::str; use std::str::FromStr; use nom::{IResult, space, eof}; -use datatype::{ClientCredentials, ClientId, ClientSecret, Error, InstalledSoftware, - Package, UpdateReport, UpdateRequestId, UpdateResultCode}; +use datatype::{ClientCredentials, Error, InstalledSoftware, Package, UpdateReport, + UpdateRequestId, UpdateResultCode}; /// System-wide commands that are sent to the interpreter. @@ -111,8 +111,9 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result<Command, Error> { 0 => Ok(Command::Authenticate(None)), 1 => Err(Error::Command("usage: auth <client-id> <client-secret>".to_string())), 2 => Ok(Command::Authenticate(Some(ClientCredentials { - client_id: ClientId(args[0].to_string()), - client_secret: ClientSecret(args[1].to_string())}))), + client_id: args[0].to_string(), + client_secret: args[1].to_string() + }))), _ => Err(Error::Command(format!("unexpected Authenticate args: {:?}", args))), }, @@ -192,8 +193,7 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result<Command, Error> { #[cfg(test)] mod tests { use super::{command, arguments}; - use datatype::{Command, ClientCredentials, ClientId, ClientSecret, Package, - UpdateReport, UpdateResultCode}; + use datatype::{Command, ClientCredentials, Package, UpdateReport, UpdateResultCode}; use nom::IResult; @@ -224,8 +224,8 @@ mod tests { assert_eq!("auth".parse::<Command>().unwrap(), Command::Authenticate(None)); assert_eq!("auth user pass".parse::<Command>().unwrap(), Command::Authenticate(Some(ClientCredentials { - client_id: ClientId("user".to_string()), - client_secret: ClientSecret("pass".to_string()), + client_id: "user".to_string(), + client_secret: "pass".to_string(), }))); assert!("auth one".parse::<Command>().is_err()); assert!("auth one two three".parse::<Command>().is_err()); diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 8267234..bb0ab4e 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -13,7 +13,7 @@ use toml::{ParserError as TomlParserError, DecodeError as TomlDecodeError}; use url::ParseError as UrlParseError; use datatype::Event; -use http::auth_client::AuthHandler; +use http::{AuthHandler, ResponseData}; use gateway::Interpret; use ws::Error as WebsocketError; @@ -21,10 +21,11 @@ use ws::Error as WebsocketError; /// System-wide errors that are returned from `Result` type failures. #[derive(Debug)] pub enum Error { - Authorization(String), Client(String), Command(String), FromUtf8(FromUtf8Error), + Http(ResponseData), + HttpAuth(ResponseData), Hyper(HyperError), HyperClient(HyperClientError<AuthHandler>), Io(IoError), @@ -76,6 +77,7 @@ derive_from!([ JsonEncoderError => JsonEncoder, JsonDecoderError => JsonDecoder, RecvError => Recv, + ResponseData => Http, TomlDecodeError => TomlDecode, UrlParseError => UrlParse, WebsocketError => Websocket @@ -92,9 +94,10 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { Error::Client(ref s) => format!("Http client error: {}", s.clone()), - Error::Authorization(ref s) => format!("Http client authorization error: {}", s.clone()), Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), Error::FromUtf8(ref e) => format!("From utf8 error: {}", e.clone()), + Error::Http(ref r) => format!("HTTP client error: {}", r.clone()), + Error::HttpAuth(ref r) => format!("HTTP authorization error: {}", r.clone()), Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), Error::HyperClient(ref e) => format!("Hyper client error: {}", e.clone()), Error::Io(ref e) => format!("IO error: {}", e.clone()), diff --git a/src/datatype/json_rpc.rs b/src/datatype/json_rpc.rs index 3eed9a2..e3a046a 100644 --- a/src/datatype/json_rpc.rs +++ b/src/datatype/json_rpc.rs @@ -1,7 +1,7 @@ use rustc_serialize::{json, Decodable, Encodable}; use time; -use http::{AuthClient, Client}; +use http::{AuthClient, Client, Response}; use super::Url; @@ -32,8 +32,12 @@ impl<E: Encodable> RpcRequest<E> { let body = json::encode(self).expect("couldn't encode RpcRequest"); let resp_rx = client.post(url, Some(body.into_bytes())); let resp = resp_rx.recv().expect("no RpcRequest response received"); - let data = try!(resp.map_err(|err| format!("{}", err))); - String::from_utf8(data).map_err(|err| format!("{}", err)) + + match resp { + Response::Success(data) => String::from_utf8(data.body).or_else(|err| Err(format!("{}", err))), + Response::Failed(data) => Err(format!("{}", data)), + Response::Error(err) => Err(format!("{}", err)) + } } } diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 6516422..017868c 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -10,7 +10,7 @@ pub mod update_report; pub mod update_request; pub mod url; -pub use self::auth::{AccessToken, Auth, ClientId, ClientSecret, ClientCredentials}; +pub use self::auth::{AccessToken, Auth, ClientCredentials}; pub use self::command::Command; pub use self::config::{AuthConfig, CoreConfig, Config, DBusConfig, DeviceConfig, GatewayConfig, RviConfig}; diff --git a/src/gateway/http.rs b/src/gateway/http.rs index 6ccc2b5..f397630 100644 --- a/src/gateway/http.rs +++ b/src/gateway/http.rs @@ -91,7 +91,7 @@ mod tests { use super::*; use gateway::{Gateway, Interpret}; use datatype::{Command, Event}; - use http::{AuthClient, Client, set_ca_certificates}; + use http::{AuthClient, Client, Response, set_ca_certificates}; #[test] @@ -124,8 +124,12 @@ mod tests { let url = "http://127.0.0.1:8888".parse().unwrap(); let body = json::encode(&cmd).unwrap(); let resp_rx = client.post(url, Some(body.into_bytes())); - let resp = resp_rx.recv().unwrap().unwrap(); - let text = String::from_utf8(resp).unwrap(); + let resp = resp_rx.recv().unwrap(); + let text = match resp { + Response::Success(data) => String::from_utf8(data.body).unwrap(), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; assert_eq!(json::decode::<Event>(&text).unwrap(), Event::FoundSystemInfo(format!("{}", id))); }); diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs index d9e28cb..2e26464 100644 --- a/src/http/auth_client.rs +++ b/src/http/auth_client.rs @@ -14,7 +14,7 @@ use std::time::Duration; use time; use datatype::{Auth, Error}; -use http::{Client, get_openssl, Request, Response}; +use http::{Client, get_openssl, Request, Response, ResponseData}; /// The `AuthClient` will attach an `Authentication` header to each outgoing @@ -52,58 +52,30 @@ impl Client for AuthClient { fn chan_request(&self, req: Request, resp_tx: Sender<Response>) { info!("{} {}", req.method, req.url); let _ = self.client.request(req.url.inner(), AuthHandler { - auth: self.auth.clone(), - req: req, - timeout: Duration::from_secs(20), - started: None, - written: 0, - response: Vec::new(), - resp_tx: resp_tx.clone(), - }).map_err(|err| resp_tx.send(Err(Error::from(err)))); + auth: self.auth.clone(), + req: req, + timeout: Duration::from_secs(20), + started: None, + written: 0, + resp_code: StatusCode::InternalServerError, + resp_body: Vec::new(), + resp_tx: resp_tx.clone(), + }).map_err(|err| resp_tx.send(Response::Error(Error::from(err)))); } } /// The async handler for outgoing HTTP requests. -// FIXME: uncomment when yocto is at 1.8.0: #[derive(Debug)] +#[derive(Debug)] pub struct AuthHandler { - auth: Auth, - req: Request, - timeout: Duration, - started: Option<u64>, - written: usize, - response: Vec<u8>, - resp_tx: Sender<Response>, -} - -// FIXME: required for building on 1.7.0 only -impl ::std::fmt::Debug for AuthHandler { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "unimplemented") - } -} - -impl AuthHandler { - fn redirect_request(&mut self, resp: HyperResponse) { - match resp.headers().get::<Location>() { - Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { - debug!("redirecting to {}", url); - // drop Authorization Header on redirect - let client = AuthClient::default(); - let resp_rx = client.send_request(Request { - url: url, - method: self.req.method.clone(), - body: mem::replace(&mut self.req.body, None), - }); - match resp_rx.recv().expect("no redirect_request response") { - Ok(data) => self.resp_tx.send(Ok(data)), - Err(err) => self.resp_tx.send(Err(Error::from(err))) - } - }).unwrap_or_else(|err| self.resp_tx.send(Err(Error::from(err)))), - - None => self.resp_tx.send(Err(Error::Client("redirect missing Location header".to_string()))) - } - } + auth: Auth, + req: Request, + timeout: Duration, + started: Option<u64>, + written: usize, + resp_code: StatusCode, + resp_body: Vec<u8>, + resp_tx: Sender<Response>, } /// The `AuthClient` may be used for both HTTP and HTTPS connections. @@ -125,15 +97,12 @@ impl Handler<Stream> for AuthHandler { headers.set(ContentType(mime_json)); } - Auth::Credentials(_, _) if self.req.body.is_some() => { - panic!("no request body expected for Auth::Credentials"); - } - - Auth::Credentials(ref id, ref secret) => { - headers.set(Authorization(Basic { username: id.0.clone(), - password: Some(secret.0.clone()) })); + Auth::Credentials(ref cred) => { + headers.set(Authorization(Basic { + username: cred.client_id.clone(), + password: Some(cred.client_secret.clone()) + })); headers.set(ContentType(mime_form)); - self.req.body = Some(br#"grant_type=client_credentials"#.to_vec()); } Auth::Token(ref token) => { @@ -173,7 +142,7 @@ impl Handler<Stream> for AuthHandler { Err(err) => { error!("unable to write request body: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::remove() } } @@ -186,32 +155,34 @@ impl Handler<Stream> for AuthHandler { let latency = time::precise_time_ns() as f64 - started as f64; debug!("on_response latency: {}ms", (latency / 1e6) as u32); - if resp.status().is_success() { - if let Some(len) = resp.headers().get::<ContentLength>() { - if **len > 0 { - return Next::read(); - } - } - self.resp_tx.send(Ok(Vec::new())); - Next::end() - } else if resp.status().is_redirection() { + self.resp_code = *resp.status(); + if resp.status().is_redirection() { self.redirect_request(resp); Next::end() - } else if resp.status() == &StatusCode::Unauthorized - || resp.status() == &StatusCode::Forbidden { - self.resp_tx.send(Err(Error::Authorization(format!("{}", resp.status())))); + } else if let None = resp.headers().get::<ContentLength>() { Next::end() } else { - self.resp_tx.send(Err(Error::Client(format!("{}", resp.status())))); - Next::end() + Next::read() } } fn on_response_readable(&mut self, decoder: &mut Decoder<Stream>) -> Next { - match io::copy(decoder, &mut self.response) { + match io::copy(decoder, &mut self.resp_body) { Ok(0) => { - debug!("on_response_readable bytes read: {}", self.response.len()); - self.resp_tx.send(Ok(mem::replace(&mut self.response, Vec::new()))); + debug!("on_response_readable body size: {}", self.resp_body.len()); + let resp = ResponseData { + code: self.resp_code, + body: mem::replace(&mut self.resp_body, Vec::new()) + }; + + if resp.code == StatusCode::Unauthorized || resp.code == StatusCode::Forbidden { + self.resp_tx.send(Response::Error(Error::HttpAuth(resp))); + } else if resp.code.is_success() { + self.resp_tx.send(Response::Success(resp)); + } else { + self.resp_tx.send(Response::Failed(resp)); + } + Next::end() } @@ -227,7 +198,7 @@ impl Handler<Stream> for AuthHandler { Err(err) => { error!("unable to read response body: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::end() } } @@ -235,11 +206,31 @@ impl Handler<Stream> for AuthHandler { fn on_error(&mut self, err: hyper::Error) -> Next { error!("on_error: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::remove() } } +impl AuthHandler { + fn redirect_request(&mut self, resp: HyperResponse) { + match resp.headers().get::<Location>() { + Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { + debug!("redirecting to {}", url); + // drop Authorization Header on redirect + let client = AuthClient::default(); + let resp_rx = client.send_request(Request { + url: url, + method: self.req.method.clone(), + body: mem::replace(&mut self.req.body, None), + }); + self.resp_tx.send(resp_rx.recv().expect("no redirect_request response")) + }).unwrap_or_else(|err| self.resp_tx.send(Response::Error(Error::from(err)))), + + None => self.resp_tx.send(Response::Error((Error::Client("redirect missing Location header".to_string())))) + } + } +} + #[cfg(test)] mod tests { @@ -247,7 +238,7 @@ mod tests { use std::path::Path; use super::*; - use http::{Client, set_ca_certificates}; + use http::{Client, Response, set_ca_certificates}; fn get_client() -> AuthClient { @@ -260,8 +251,13 @@ mod tests { let client = get_client(); let url = "http://eu.httpbin.org/bytes/16?seed=123".parse().unwrap(); let resp_rx = client.get(url, None); - let data = resp_rx.recv().unwrap().unwrap(); - assert_eq!(data, vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]); + let resp = resp_rx.recv().unwrap(); + let expect = vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]; + match resp { + Response::Success(data) => assert_eq!(data.body, expect), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; } #[test] @@ -269,9 +265,13 @@ mod tests { let client = get_client(); let url = "https://eu.httpbin.org/post".parse().unwrap(); let resp_rx = client.post(url, Some(br#"foo"#.to_vec())); - let body = resp_rx.recv().unwrap().unwrap(); - let resp = String::from_utf8(body).unwrap(); - let json = Json::from_str(&resp).unwrap(); + let resp = resp_rx.recv().unwrap(); + let body = match resp { + Response::Success(data) => String::from_utf8(data.body).unwrap(), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; + let json = Json::from_str(&body).unwrap(); let obj = json.as_object().unwrap(); let data = obj.get("data").unwrap().as_string().unwrap(); assert_eq!(data, "foo"); diff --git a/src/http/http_client.rs b/src/http/http_client.rs index 492166c..b911b8d 100644 --- a/src/http/http_client.rs +++ b/src/http/http_client.rs @@ -1,5 +1,8 @@ use chan; use chan::{Sender, Receiver}; +use hyper::status::StatusCode; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::str; use datatype::{Error, Method, Url}; @@ -39,5 +42,42 @@ pub struct Request { pub body: Option<Vec<u8>> } -/// Return the body of an HTTP response on success, or an `Error` otherwise. -pub type Response = Result<Vec<u8>, Error>; + +/// A Response enumerates between a successful (e.g. 2xx) HTTP response, a failed +/// (e.g. 4xx/5xx) response, or an Error before receiving any response. +#[derive(Debug)] +pub enum Response { + Success(ResponseData), + Failed(ResponseData), + Error(Error) +} + +impl Display for Response { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match *self { + Response::Success(ref data) => write!(f, "{}", data), + Response::Failed(ref data) => write!(f, "{}", data), + Response::Error(ref err) => write!(f, "{}", err), + } + } +} + + +/// Wraps the HTTP Status Code as well as any returned body. +#[derive(Debug)] +pub struct ResponseData { + pub code: StatusCode, + pub body: Vec<u8> +} + +impl Display for ResponseData { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self.body.len() { + 0 => write!(f, "Response Code: {}", self.code), + n => match str::from_utf8(&self.body) { + Ok(text) => write!(f, "Response Code: {}, Body:\n{}", self.code, text), + Err(_) => write!(f, "Response Code: {}, Body: {} bytes", self.code, n), + } + } + } +} diff --git a/src/http/mod.rs b/src/http/mod.rs index 5e990a3..11b1e3a 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -5,7 +5,7 @@ pub mod openssl; pub mod test_client; pub use self::auth_client::{AuthClient, AuthHandler}; -pub use self::http_client::{Client, Request, Response}; +pub use self::http_client::{Client, Request, Response, ResponseData}; pub use self::http_server::{Server, ServerHandler}; pub use self::openssl::{get_openssl, set_ca_certificates}; pub use self::test_client::TestClient; diff --git a/src/http/test_client.rs b/src/http/test_client.rs index 7857e0f..1886fdf 100644 --- a/src/http/test_client.rs +++ b/src/http/test_client.rs @@ -1,8 +1,9 @@ use chan::Sender; +use hyper::status::StatusCode; use std::cell::RefCell; use datatype::Error; -use http::{Client, Request, Response}; +use http::{Client, Request, Response, ResponseData}; /// The `TestClient` will return HTTP responses from an existing list of strings. @@ -26,8 +27,11 @@ impl TestClient { impl Client for TestClient { fn chan_request(&self, req: Request, resp_tx: Sender<Response>) { match self.responses.borrow_mut().pop() { - Some(body) => resp_tx.send(Ok(body.as_bytes().to_vec())), - None => resp_tx.send(Err(Error::Client(req.url.to_string()))) + Some(body) => resp_tx.send(Response::Success(ResponseData { + code: StatusCode::Ok, + body: body.as_bytes().to_vec() + })), + None => resp_tx.send(Response::Error(Error::Client(req.url.to_string()))) } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 75e6e8b..00ad37c 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3,9 +3,8 @@ use chan::{Sender, Receiver}; use std; use std::borrow::Cow; -use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, - Error, Event, Package, UpdateReport, UpdateRequestStatus as Status, - UpdateResultCode}; +use datatype::{AccessToken, Auth, ClientCredentials, Command, Config, Error, Event, + Package, UpdateReport, UpdateRequestStatus as Status, UpdateResultCode}; use gateway::Interpret; use http::{AuthClient, Client}; use oauth2::authenticate; @@ -38,13 +37,12 @@ impl Interpreter<Event, Command> for EventInterpreter { info!("Event received: {}", event); match event { Event::Authenticated => { - ctx.send(Command::SendSystemInfo); - if self.package_manager != PackageManager::Off { self.package_manager.installed_packages().map(|packages| { ctx.send(Command::SendInstalledPackages(packages)); }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err)); } + ctx.send(Command::SendSystemInfo); } Event::NotAuthenticated => { @@ -141,24 +139,22 @@ impl<'t> Interpreter<Interpret, Event> for GlobalInterpreter<'t> { etx.send(ev.clone()); response_ev = Some(ev); } - info!("Interpreter finished."); } - Err(Error::Authorization(_)) => { + Err(Error::HttpAuth(_)) => { let ev = Event::NotAuthenticated; etx.send(ev.clone()); response_ev = Some(ev); - error!("Interpreter authentication failed"); } Err(err) => { let ev = Event::Error(format!("{}", err)); etx.send(ev.clone()); response_ev = Some(ev); - error!("Interpreter failed: {}", err); } } + info!("Interpreter finished."); let ev = response_ev.expect("no response event to send back"); interpret.response_tx.map(|tx| tx.lock().unwrap().send(ev)); } @@ -250,8 +246,10 @@ impl<'t> GlobalInterpreter<'t> { match cmd { Command::Authenticate(_) => { let config = self.config.auth.clone().expect("trying to authenticate without auth config"); - self.set_client(Auth::Credentials(ClientId(config.client_id), - ClientSecret(config.client_secret))); + self.set_client(Auth::Credentials(ClientCredentials { + client_id: config.client_id, + client_secret: config.client_secret, + })); let server = config.server.join("/token").expect("couldn't build authentication url"); let token = try!(authenticate(server, self.http_client.as_ref())); self.set_client(Auth::Token(token.clone())); diff --git a/src/oauth2.rs b/src/oauth2.rs index 0c5f152..e34e4c2 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -1,16 +1,19 @@ use rustc_serialize::json; use datatype::{AccessToken, Error, Url}; -use http::Client; +use http::{Client, Response}; /// Authenticate with the specified OAuth2 server to retrieve a new `AccessToken`. pub fn authenticate(server: Url, client: &Client) -> Result<AccessToken, Error> { debug!("authenticating at {}", server); - let resp_rx = client.post(server, None); + let resp_rx = client.post(server, Some(br#"grant_type=client_credentials"#.to_vec())); let resp = resp_rx.recv().expect("no authenticate response received"); - let data = try!(resp); - let body = try!(String::from_utf8(data)); + let body = match resp { + Response::Success(data) => try!(String::from_utf8(data.body)), + Response::Failed(data) => return Err(Error::from(data)), + Response::Error(err) => return Err(err) + }; Ok(try!(json::decode(&body))) } diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 99aacbf..beab7dd 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -23,7 +23,7 @@ pub fn installed_packages() -> Result<Vec<Package>, Error> { }) } -/// Installs a new RPM package. +/// Installs a new RPM package with `rpm -Uvh --force <package-path>`. pub fn install_package(path: &str) -> Result<InstallOutcome, InstallOutcome> { let output = try!(Command::new("rpm").arg("-Uvh").arg("--force").arg(path) .output() diff --git a/src/sota.rs b/src/sota.rs index 6c48424..9ea615e 100644 --- a/src/sota.rs +++ b/src/sota.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use datatype::{Config, DeviceReport, DownloadComplete, Error, Package, UpdateReport, UpdateRequest, UpdateRequestId, Url}; -use http::Client; +use http::{Client, Response}; /// Encapsulate the client configuration and HTTP client used for @@ -38,20 +38,31 @@ impl<'c, 'h> Sota<'c, 'h> { /// Query the Core server for any pending or in-flight package updates. pub fn get_update_requests(&mut self) -> Result<Vec<UpdateRequest>, Error> { - let _ = self.client.get(self.endpoint(""), None); // FIXME(PRO-1352): single endpoint let resp_rx = self.client.get(self.endpoint("/queued"), None); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get new updates".to_string()))); - let text = try!(String::from_utf8(try!(resp))); + let data = match resp { + Response::Success(data) => data, + Response::Failed(data) => return Err(Error::from(data)), + Response::Error(err) => return Err(err) + }; + + let text = try!(String::from_utf8(data.body)); Ok(try!(json::decode::<Vec<UpdateRequest>>(&text))) } /// Download a specific update from the Core server. pub fn download_update(&mut self, id: UpdateRequestId) -> Result<DownloadComplete, Error> { - let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None); - let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string()))); + let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string()))); + let data = match resp { + Response::Success(data) => data, + Response::Failed(data) => return Err(Error::from(data)), + Response::Error(err) => return Err(err) + }; + let path = try!(self.package_path(id.clone())); let mut file = try!(File::create(&path)); - let _ = io::copy(&mut &*try!(resp), &mut file); + let _ = io::copy(&mut &*data.body, &mut file); Ok(DownloadComplete { update_id: id, update_image: path.to_string(), @@ -75,8 +86,12 @@ impl<'c, 'h> Sota<'c, 'h> { let body = try!(json::encode(packages)); let resp_rx = self.client.put(self.endpoint("/installed"), Some(body.into_bytes())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send installed packages".to_string()))); - let _ = try!(resp); - Ok(()) + + match resp { + Response::Success(_) => Ok(()), + Response::Failed(data) => Err(Error::from(data)), + Response::Error(err) => Err(err) + } } /// Send the outcome of a package update to the Core server. @@ -86,16 +101,24 @@ impl<'c, 'h> Sota<'c, 'h> { let url = self.endpoint(&format!("/{}", report.device)); let resp_rx = self.client.post(url, Some(body.into_bytes())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send update report".to_string()))); - let _ = try!(resp); - Ok(()) + + match resp { + Response::Success(_) => Ok(()), + Response::Failed(data) => Err(Error::from(data)), + Response::Error(err) => Err(err) + } } /// Send system information from the device to the Core server. pub fn send_system_info(&mut self, body: &str) -> Result<(), Error> { let resp_rx = self.client.put(self.endpoint("/system_info"), Some(body.as_bytes().to_vec())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send system info".to_string()))); - let _ = try!(resp); - Ok(()) + + match resp { + Response::Success(_) => Ok(()), + Response::Failed(data) => Err(Error::from(data)), + Response::Error(err) => Err(err) + } } } @@ -125,7 +148,7 @@ mod tests { let json = format!("[{}]", json::encode(&pending_update).unwrap()); let mut sota = Sota { config: &Config::default(), - client: &mut TestClient::from(vec![json.to_string(), "[]".to_string()]), + client: &mut TestClient::from(vec![json.to_string()]), }; let updates: Vec<UpdateRequest> = sota.get_update_requests().unwrap(); |