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

Another day, Another session. #272

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
13 changes: 10 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ before_script:

script:
- |
travis-cargo build &&
travis-cargo test &&
travis-cargo doc &&
travis-cargo build $FEATURES &&
travis-cargo test $FEATURES &&
travis-cargo doc $FEATURES &&
echo "Testing README" &&
rustdoc --test README.md -L dependency=./target/debug/deps --extern nickel=./target/debug/libnickel.rlib

Expand All @@ -34,3 +34,10 @@ after_success:
env:
global:
secure: nPXTdkq9TK4LmqJWqB4jxscDG1mnqJzO0UX5ni+oC0SohjgBxdWjbvJ8Kthk3c8Qg/2zGlCryuT1lV4TcD3jF86MHlRlXsO3G8fCbEGdLLzppbV0A2VnEw1knhv2mfUhxA8hsFJE2ncT5qeOVyQ6N3PAMGoYkdhhbD9N/9EWuqs=
matrix:
# travis-cargo requires prefixing additional args with --
- FEATURES=""
- FEATURES="-- --features ssl"
- FEATURES="-- --features session"
- FEATURES="-- --features secure_cookies"
- FEATURES="-- --no-default-features"
17 changes: 16 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ keywords = ["nickel", "server", "web", "express"]
[features]
unstable = ["hyper/nightly", "compiletest_rs"]
ssl = ["hyper/ssl"]
secure_cookies = ["cookie/secure"]
session = ["secure_cookies"]

[dependencies]
url = "*"
Expand All @@ -28,9 +30,12 @@ groupable = "*"
mustache = "*"
lazy_static = "*"
modifier = "*"
cookie = "^0.1"
byteorder = "^0.3"
rand = "^0.3"

[dependencies.hyper]
version = "=0.6"
version = "^0.6"
default-features = false

[dependencies.compiletest_rs]
Expand All @@ -44,6 +49,16 @@ path = "examples/example.rs"

[[example]]

name = "session_example"
path = "examples/session_example.rs"

[[example]]

name = "cookies_example"
path = "examples/cookies_example.rs"

[[example]]

name = "example_with_default_router"
path = "examples/example_with_default_router.rs"

Expand Down
59 changes: 59 additions & 0 deletions examples/cookies_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#[macro_use] extern crate nickel;
extern crate cookie;

use nickel::{Nickel, HttpRouter, Cookies, QueryString};
use cookie::Cookie;

fn main() {
let mut server = Nickel::new();

// Try curl -b MyCookie=bar localhost:6767
server.get("/", middleware! { |req|
let cookie = req.cookies().find("MyCookie");
format!("MyCookie={:?}", cookie.map(|c| c.value))
});

// Note: Don't use get for login in real applications ;)
// Try http://localhost:6767/login?name=foo
server.get("/login", middleware! { |req, mut res|
let jar = res.cookies_mut()
// long life cookies!
.permanent();

let name = req.query().get("name")
.unwrap_or("default_name");
let cookie = Cookie::new("MyCookie".to_owned(),
name.to_owned());
jar.add(cookie);

"Cookie set!"
});

// Try `curl -c /tmp/cookie -b /tmp/cookie http://localhost:6767/secure?value=foobar`
// when the `secure_cookies` feature is enabled
// i.e. `cargo run --example cookies_example --features secure_cookies
if cfg!(feature = "secure_cookies") {
server.get("/secure", middleware! { |req, mut res|
let jar = res.cookies_mut()
.encrypted();

let new_value = req.query().get("value")
.unwrap_or("no value")
.to_owned();

let cookie = Cookie::new("SecureCookie".to_owned(),
new_value);
jar.add(cookie);

// Old value from the request's Cookies
let old_value = req.cookies()
.encrypted()
.find("SecureCookie")
.map(|c| c.value);

format!("Old value was {:?}", old_value)
});
}

server.listen("127.0.0.1:6767");
}
4 changes: 2 additions & 2 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ fn main() {
server.utilize(StaticFilesHandler::new("examples/assets/"));

//this is how to overwrite the default error handler to handle 404 cases with a custom view
fn custom_404<'a>(err: &mut NickelError, _req: &mut Request) -> Action {
fn custom_404<'a, D>(err: &mut NickelError<D>, _req: &mut Request<D>) -> Action {
if let Some(ref mut res) = err.stream {
if res.status() == NotFound {
let _ = res.write_all(b"<h1>Call the police!</h1>");
Expand All @@ -126,7 +126,7 @@ fn main() {


// issue #20178
let custom_handler: fn(&mut NickelError, &mut Request) -> Action = custom_404;
let custom_handler: fn(&mut NickelError<()>, &mut Request<()>) -> Action = custom_404;

server.handle_error(custom_handler);

Expand Down
6 changes: 3 additions & 3 deletions examples/macro_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ struct Person {
}

//this is an example middleware function that just logs each request
fn logger<'a>(request: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
fn logger<'a, D>(request: &mut Request<D>, response: Response<'a, D>) -> MiddlewareResult<'a, D> {
println!("logging request: {:?}", request.origin.uri);
Ok(Continue(response))
}

//this is how to overwrite the default error handler to handle 404 cases with a custom view
fn custom_404<'a>(err: &mut NickelError, _req: &mut Request) -> Action {
fn custom_404<'a, D>(err: &mut NickelError<D>, _req: &mut Request<D>) -> Action {
if let Some(ref mut res) = err.stream {
if res.status() == NotFound {
let _ = res.write_all(b"<h1>Call the police!</h1>");
Expand Down Expand Up @@ -122,7 +122,7 @@ fn main() {
));

// issue #20178
let custom_handler: fn(&mut NickelError, &mut Request) -> Action = custom_404;
let custom_handler: fn(&mut NickelError<()>, &mut Request<()>) -> Action = custom_404;

server.handle_error(custom_handler);

Expand Down
82 changes: 82 additions & 0 deletions examples/session_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! This example only works if you enable the `session` feature.
#![cfg_attr(not(feature = "session"), allow(dead_code, unused_imports))]

#[macro_use] extern crate nickel;
extern crate rustc_serialize;
extern crate time;

use std::io::Write;
use nickel::*;
use nickel::status::StatusCode;
use time::Duration;

#[derive(RustcDecodable, RustcEncodable)]
struct User {
name: String,
password: String,
}

struct ServerData;
static SECRET_KEY: &'static cookies::SecretKey = &cookies::SecretKey([0; 32]);

#[cfg(feature = "session")]
impl cookies::KeyProvider for ServerData {
fn key(&self) -> cookies::SecretKey { SECRET_KEY.clone() }
}

#[cfg(feature = "session")]
impl session::Store for ServerData {
type Session = Option<String>;

fn timeout() -> Duration {
Duration::seconds(5)
}
}

#[cfg(not(feature = "session"))]
fn main() {}

#[cfg(feature = "session")]
fn main() {
let mut server = Nickel::with_data(ServerData);

// Anyone should be able to reach this route
server.get("/", middleware! { |req, mut res|
format!("You are logged in as: {:?}\n", CookieSession::get_mut(req, &mut res))
});

server.post("/login", middleware!{|req, mut res|
if let Ok(u) = req.json_as::<User>() {
if u.name == "foo" && u.password == "bar" {
*CookieSession::get_mut(req, &mut res) = Some(u.name);
return res.send("Successfully logged in.")
}
}
(StatusCode::BadRequest, "Access denied.")
});

server.get("/secret", middleware! { |req, mut res| <ServerData>
match *CookieSession::get_mut(req, &mut res) {
Some(ref user) if user == "foo" => (StatusCode::Ok, "Some hidden information!"),
_ => (StatusCode::Forbidden, "Access denied.")
}
});

fn custom_403<'a>(err: &mut NickelError<ServerData>, _: &mut Request<ServerData>) -> Action {
if let Some(ref mut res) = err.stream {
if res.status() == StatusCode::Forbidden {
let _ = res.write_all(b"Access denied!\n");
return Halt(())
}
}

Continue(())
}

// issue #20178
let custom_handler: fn(&mut NickelError<ServerData>, &mut Request<ServerData>) -> Action = custom_403;

server.handle_error(custom_handler);

server.listen("127.0.0.1:6767");
}
146 changes: 146 additions & 0 deletions src/cookies.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use {Request, Response};
use plugin::{Plugin, Pluggable};
use typemap::Key;
use hyper::header;
use rand::{self, Rng};

use cookie::CookieJar;

#[derive(Clone)]
// Let's not derive `Copy` as that seems like a bad idea for key data
pub struct SecretKey(pub [u8; 32]);

impl SecretKey {
pub fn new<T: AsRef<[u8]>>(arr: T) -> Result<SecretKey, &'static str> {
let arr = arr.as_ref();
if arr.len() != 32 { return Err("Key length must be 32") }

let mut key = [0; 32];
for idx in 0..32 {
key[idx] = arr[idx]
}

Ok(SecretKey(key))
}
}

// Plugin boilerplate
pub struct CookiePlugin;
impl Key for CookiePlugin { type Value = CookieJar<'static>; }

impl<'mw, 'conn, D> Plugin<Request<'mw, 'conn, D>> for CookiePlugin
where D: KeyProvider {
type Error = ();

fn eval(req: &mut Request<D>) -> Result<CookieJar<'static>, ()> {
let key = req.data().key();
let jar = match req.origin.headers.get::<header::Cookie>() {
Some(c) => c.to_cookie_jar(&key.0),
None => CookieJar::new(&key.0)
};

Ok(jar)
}
}

impl<'a, 'b, 'k, D> Plugin<Response<'a, D>> for CookiePlugin
where D: KeyProvider {
type Error = ();

fn eval(res: &mut Response<'a, D>) -> Result<CookieJar<'static>, ()> {
// Schedule the cookie to be written when headers are being sent
res.on_send(|res| {
let header = {
let jar = res.get_ref::<CookiePlugin>().unwrap();
header::SetCookie::from_cookie_jar(jar)
};
res.set(header);
});

let key = res.data().key();
Ok(CookieJar::new(&key.0))
}
}

/// Trait to whitelist access to `&'mut CookieJar` via the `Cookies` trait.
pub trait AllowMutCookies {}
impl<'a, D> AllowMutCookies for Response<'a, D> {}

/// Provide the key used for decoding secure CookieJars
///
/// Cookies require a random key for their signed and encrypted cookies to be
/// used.
///
/// Implementors should aim to provide a stable key between server reboots so
/// as to minimize data loss in client cookies.
///
/// # Implementing
///
/// The `secure_cookies` feature needs to be enabled for this to be implementable
pub trait KeyProvider {
fn key(&self) -> SecretKey {
lazy_static! {
static ref CACHED_SECRET: SecretKey = {
let mut rng = rand::thread_rng();
let bytes: Vec<u8> = (0..32).map(|_| rng.gen()).collect();

SecretKey::new(bytes).unwrap()
};
};

CACHED_SECRET.clone()
}
}

#[cfg(feature = "secure_cookies")]
impl KeyProvider for () {}

#[cfg(not(feature = "secure_cookies"))]
impl<T> KeyProvider for T {
fn key(&self) -> SecretKey {
SecretKey([0; 32])
}
}

/// Provides access to a `CookieJar`.
///
/// Access to cookies for a `Request` is read-only and represents the cookies
/// sent from the client.
///
/// The `Response` has access to a mutable `CookieJar` when first accessed.
/// Any cookies added to this jar will be sent as `Set-Cookie` response headers
/// when the `Response` sends it's `Headers` to the client.
///
/// #Examples
/// See `examples/cookies_example.rs`.
pub trait Cookies {
/// Provides access to an immutable CookieJar.
///
/// Currently requires a mutable reciever, hopefully this can change in future.
fn cookies(&mut self) -> &CookieJar<'static>;

/// Provides access to a mutable CookieJar.
fn cookies_mut(&mut self) -> &mut CookieJar<'static> where Self: AllowMutCookies;
}

impl<'mw, 'conn, D> Cookies for Request<'mw, 'conn, D>
where D: KeyProvider {
fn cookies(&mut self) -> &<CookiePlugin as Key>::Value {
self.get_ref::<CookiePlugin>().unwrap()
}

fn cookies_mut(&mut self) -> &mut <CookiePlugin as Key>::Value where Self: AllowMutCookies {
unreachable!()
}
}

impl<'mw, D> Cookies for Response<'mw, D>
where D: KeyProvider {
fn cookies(&mut self) -> &<CookiePlugin as Key>::Value {
self.get_ref::<CookiePlugin>().unwrap()
}

fn cookies_mut(&mut self) -> &mut <CookiePlugin as Key>::Value where Self: AllowMutCookies {
self.get_mut::<CookiePlugin>().unwrap()
}
}
Loading