From a7e9cf24aaa70f2ec72fd25af4909157af07ce33 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 15 Jul 2014 08:02:56 -0700 Subject: [PATCH] Add a simple s3 lib --- Cargo.toml | 4 +++ src/app.rs | 26 ++++++++++++---- src/main.rs | 1 + src/s3/Cargo.toml | 18 +++++++++++ src/s3/lib.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++ src/user/mod.rs | 2 +- 6 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 src/s3/Cargo.toml create mode 100644 src/s3/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e22a9627eb..c96a5b4f42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,10 @@ git = "https://github.com/sfackler/rust-postgres" path = "src/html" +[dependencies.s3] + +path = "src/s3" + [dependencies.oauth2] git = "https://github.com/alexcrichton/oauth2-rs" diff --git a/src/app.rs b/src/app.rs index 79bc1440c1..55e9a91e33 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,12 @@ -use oauth2; +use std::any::AnyRefExt; +use std::fmt::Show; +use std::os; + use conduit::Request; use conduit_middleware::Middleware; +use oauth2; use pg::pool::{PostgresConnectionPool, PooledPostgresConnection}; -use std::any::AnyRefExt; -use std::fmt::Show; +use s3; use db; @@ -12,6 +15,7 @@ use std::sync::Arc; pub struct App { db: PostgresConnectionPool, pub github: oauth2::Config, + pub bucket: s3::Bucket, } pub struct AppMiddleware { @@ -23,15 +27,25 @@ impl App { let pool = db::pool(); db::setup(&*pool.get_connection()); let github = oauth2::Config::new( - "89b6afdeaa6c6c7506ec", - "7a4908a38c75dd12bce36931ad2dbdd951ce228b", + env("GH_CLIENT_ID").as_slice(), + env("GH_CLIENT_SECRET").as_slice(), "https://github.com/login/oauth/authorize", "https://github.com/login/oauth/access_token", ); - App { + return App { db: db::pool(), github: github, + bucket: s3::Bucket::new(env("S3_BUCKET"), + env("S3_ACCESS_KEY"), + env("S3_SECRET_KEY")), + }; + + fn env(s: &str) -> String { + match os::getenv(s) { + Some(s) => s, + None => fail!("must have `{}` defined", s), + } } } diff --git a/src/main.rs b/src/main.rs index 7d333110ef..6a45384428 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ extern crate curl; extern crate html; extern crate oauth2; extern crate pg = "postgres"; +extern crate s3; extern crate conduit_router = "conduit-router"; extern crate conduit; diff --git a/src/s3/Cargo.toml b/src/s3/Cargo.toml new file mode 100644 index 0000000000..cd686e3966 --- /dev/null +++ b/src/s3/Cargo.toml @@ -0,0 +1,18 @@ +[package] + +name = "s3" +version = "0.0.1" +authors = [] + +[[lib]] + +name = "s3" +path = "lib.rs" + +[dependencies.curl] + +git = "https://github.com/carllerche/curl-rust" + +[dependencies.openssl] + +git = "https://github.com/sfackler/rust-openssl" diff --git a/src/s3/lib.rs b/src/s3/lib.rs new file mode 100644 index 0000000000..66b5b2a121 --- /dev/null +++ b/src/s3/lib.rs @@ -0,0 +1,77 @@ +extern crate time; +extern crate curl; +extern crate serialize; +extern crate openssl; + +use curl::http; +use curl::http::body::ToBody; +use openssl::crypto::{hmac, hash}; +use serialize::base64::{ToBase64, STANDARD}; + +pub struct Bucket { + name: String, + access_key: String, + secret_key: String, +} + +impl Bucket { + pub fn new(name: String, + access_key: String, + secret_key: String) -> Bucket { + Bucket { + name: name, + access_key: access_key, + secret_key: secret_key, + } + } + + pub fn put<'a, T: ToBody<'a>>(&mut self, path: &str, content: T) + -> Result { + let mut handle = http::handle(); + let host = self.host(); + let date = time::now().rfc822z(); + let auth = self.auth("PUT", date.as_slice(), path, "", + "application/octet-stream"); + let url = format!("https://{}/{}", host, path); + handle.put(url.as_slice(), content) + .header("Host", host.as_slice()) + .header("Date", date.as_slice()) + .header("Authorization", auth.as_slice()) + .exec() + } + + pub fn delete(&mut self, path: &str) + -> Result { + let mut handle = http::handle(); + let host = self.host(); + let date = time::now().rfc822z(); + let auth = self.auth("DELETE", date.as_slice(), path, "", ""); + let url = format!("https://{}/{}", host, path); + handle.delete(url.as_slice()) + .header("Host", host.as_slice()) + .header("Date", date.as_slice()) + .header("Authorization", auth.as_slice()) + .exec() + } + + fn host(&self) -> String { + format!("{}.s3.amazonaws.com", self.name) + } + + fn auth(&self, verb: &str, date: &str, path: &str, + md5: &str, content_type: &str) -> String { + let string = format!("{verb}\n{md5}\n{ty}\n{date}\n{headers}{resource}", + verb = verb, + md5 = md5, + ty = content_type, + date = date, + headers = "", + resource = format!("/{}/{}", self.name, path)); + let signature = { + let mut hmac = hmac::HMAC(hash::SHA1, self.secret_key.as_bytes()); + hmac.update(string.as_bytes()); + hmac.final().as_slice().to_base64(STANDARD) + }; + format!("AWS {}:{}", self.access_key, signature) + } +} diff --git a/src/user/mod.rs b/src/user/mod.rs index 57d1ba0a90..cb1ee899c5 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -49,7 +49,7 @@ impl User { } pub fn new_api_token() -> String { - task_rng().gen_ascii_chars().take(16).collect() + task_rng().gen_ascii_chars().take(32).collect() } pub fn encodable(self) -> EncodableUser {