Update to cookie 0.7. Use 256-bit session_keys.

This commit involves several breaking changes:
  * `session_key` config param must be a 256-bit base64 encoded string.
  * `FromRequest` is implemented for `Cookies`, not `Cookie`.
  * Only a single `Cookies` instance can be retrieved at a time.
  * `Config::take_session_key` returns a `Vec<u8>`.
  * `Into<Header>` is implemented for `&Cookie`, not `Cookie`.
This commit is contained in:
Sergio Benitez 2017-03-07 01:19:06 -08:00
parent 6be902162d
commit 722ee93f8b
13 changed files with 129 additions and 75 deletions

View File

@ -16,7 +16,7 @@ struct User<'a> {
fn get<'r>(name: &str,
query: User<'r>,
user: Form<'r, User<'r>>,
cookies: &Cookies)
cookies: Cookies)
-> &'static str {
"hi"
}

View File

@ -15,7 +15,7 @@ port = 80
log = "normal"
workers = 8
# don't use this key! generate your own and keep it private!
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
session_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
[production]
address = "0.0.0.0"
@ -23,4 +23,4 @@ port = 80
workers = 12
log = "critical"
# don't use this key! generate your own and keep it private!
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
session_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="

View File

@ -20,14 +20,14 @@ struct Message {
}
#[post("/submit", data = "<message>")]
fn submit(cookies: &Cookies, message: Form<Message>) -> Redirect {
fn submit(mut cookies: Cookies, message: Form<Message>) -> Redirect {
cookies.add(Cookie::new("message", message.into_inner().message));
Redirect::to("/")
}
#[get("/")]
fn index(cookies: &Cookies) -> Template {
let cookie = cookies.find("message");
fn index(cookies: Cookies) -> Template {
let cookie = cookies.get("message");
let mut context = HashMap::new();
if let Some(ref cookie) = cookie {
context.insert("message", cookie.value());

View File

@ -25,10 +25,13 @@ state = "^0.2"
time = "^0.1"
memchr = "1"
# FIXME: session support should be optional
base64 = "0.4"
# FIXME: session support should be optional
[dependencies.cookie]
version = "^0.6"
default-features = false
features = ["percent-encode"]
version = "^0.7"
features = ["percent-encode", "secure"]
[dev-dependencies]
lazy_static = "0.2"

View File

@ -152,12 +152,12 @@ impl ConfigBuilder {
/// use rocket::LoggingLevel;
/// use rocket::config::{Config, Environment};
///
/// let key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5";
/// let key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=";
/// let mut config = Config::build(Environment::Staging)
/// .session_key(key)
/// .unwrap();
///
/// assert_eq!(config.take_session_key(), Some(key.to_string()));
/// assert!(config.take_session_key().is_some());
/// ```
pub fn session_key<K: Into<String>>(mut self, key: K) -> Self {
self.session_key = Some(key.into());

View File

@ -9,7 +9,7 @@ use std::env;
use config::Environment::*;
use config::{self, Value, ConfigBuilder, Environment, ConfigError};
use num_cpus;
use {num_cpus, base64};
use logger::LoggingLevel;
/// Structure for Rocket application configuration.
@ -44,7 +44,7 @@ pub struct Config {
/// The path to the configuration file this config belongs to.
pub config_path: PathBuf,
/// The session key.
session_key: RwLock<Option<String>>,
session_key: RwLock<Option<Vec<u8>>>,
}
macro_rules! parse {
@ -175,12 +175,6 @@ impl Config {
ConfigError::BadType(id, expect, actual, self.config_path.clone())
}
// Aliases to `set` before the method is removed.
pub(crate) fn set_raw(&mut self, name: &str, val: &Value) -> config::Result<()> {
#[allow(deprecated)]
self.set(name, val)
}
/// Sets the configuration `val` for the `name` entry. If the `name` is one
/// of "address", "port", "session_key", "log", or "workers" (the "default"
/// values), the appropriate value in the `self` Config structure is set.
@ -195,8 +189,7 @@ impl Config {
/// * **workers**: Integer (16-bit unsigned)
/// * **log**: String
/// * **session_key**: String (192-bit base64)
#[deprecated(since="0.2", note="use the set_{param} methods instead")]
pub fn set(&mut self, name: &str, val: &Value) -> config::Result<()> {
pub(crate) fn set_raw(&mut self, name: &str, val: &Value) -> config::Result<()> {
if name == "address" {
let address_str = parse!(self, name, val, as_str, "a string")?;
self.set_address(address_str)?;
@ -335,20 +328,27 @@ impl Config {
/// # use rocket::config::ConfigError;
/// # fn config_test() -> Result<(), ConfigError> {
/// let mut config = Config::new(Environment::Staging)?;
/// assert!(config.set_session_key("VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5").is_ok());
/// let key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=";
/// assert!(config.set_session_key(key).is_ok());
/// assert!(config.set_session_key("hello? anyone there?").is_err());
/// # Ok(())
/// # }
/// ```
pub fn set_session_key<K: Into<String>>(&mut self, key: K) -> config::Result<()> {
let key = key.into();
if key.len() != 32 {
return Err(self.bad_type("session_key",
"string",
"a 192-bit base64 string"));
let error = self.bad_type("session_key", "string",
"a 256-bit base64 encoded string");
if key.len() != 44 {
return Err(error);
}
self.session_key = RwLock::new(Some(key));
let bytes = match base64::decode(&key) {
Ok(bytes) => bytes,
Err(_) => return Err(error)
};
self.session_key = RwLock::new(Some(bytes));
Ok(())
}
@ -435,21 +435,21 @@ impl Config {
/// use rocket::config::{Config, Environment};
///
/// // Create a new config with a session key.
/// let key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz";
/// let key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=";
/// let config = Config::build(Environment::Staging)
/// .session_key(key)
/// .unwrap();
///
/// // Get the key for the first time.
/// let session_key = config.take_session_key();
/// assert_eq!(session_key, Some(key.to_string()));
/// assert!(session_key.is_some());
///
/// // Try to get the key again.
/// let session_key_again = config.take_session_key();
/// assert_eq!(session_key_again, None);
/// ```
#[inline]
pub fn take_session_key(&self) -> Option<String> {
pub fn take_session_key(&self) -> Option<Vec<u8>> {
let mut key = self.session_key.write().expect("couldn't lock session key");
key.take()
}

View File

@ -40,9 +40,9 @@
//! * examples: `12`, `1`, `4`
//! * **log**: _[string]_ how much information to log; one of `"normal"`,
//! `"debug"`, or `"critical"`
//! * **session_key**: _[string]_ a 192-bit base64 encoded string (32
//! * **session_key**: _[string]_ a 256-bit base64 encoded string (44
//! characters) to use as the session key
//! * example: `"VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"`
//! * example: `"8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="`
//!
//! ### Rocket.toml
//!
@ -70,7 +70,7 @@
//! workers = max(number_of_cpus, 2)
//! log = "normal"
//! # don't use this key! generate your own and keep it private!
//! session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
//! session_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
//!
//! [production]
//! address = "0.0.0.0"
@ -78,7 +78,7 @@
//! workers = max(number_of_cpus, 2)
//! log = "critical"
//! # don't use this key! generate your own and keep it private!
//! session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
//! session_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
//! ```
//!
//! The `workers` parameter is computed by Rocket automatically; the value above
@ -587,7 +587,7 @@ mod test {
port = 7810
workers = 21
log = "critical"
session_key = "01234567890123456789012345678901"
session_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
template_dir = "mine"
json = true
pi = 3.14
@ -598,7 +598,7 @@ mod test {
.port(7810)
.workers(21)
.log_level(LoggingLevel::Critical)
.session_key("01234567890123456789012345678901")
.session_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=")
.extra("template_dir", "mine")
.extra("json", true)
.extra("pi", 3.14);
@ -873,19 +873,19 @@ mod test {
check_config!(RocketConfig::parse(r#"
[stage]
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
session_key = "TpUiXK2d/v5DFxJnWL12suJKPExKR8h9zd/o+E7SU+0="
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).session_key(
"VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
"TpUiXK2d/v5DFxJnWL12suJKPExKR8h9zd/o+E7SU+0="
)
});
check_config!(RocketConfig::parse(r#"
[stage]
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
session_key = "jTyprDberFUiUFsJ3vcb1XKsYHWNBRvWAnXTlbTgGFU="
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).session_key(
"adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
"jTyprDberFUiUFsJ3vcb1XKsYHWNBRvWAnXTlbTgGFU="
)
});
}

View File

@ -1,17 +1,62 @@
use http::Header;
pub use cookie::Cookie;
pub use cookie::CookieJar;
pub use cookie::CookieBuilder;
use std::cell::RefMut;
/// Type alias to a `'static` CookieJar.
///
/// A `CookieJar` should never be used without a `'static` lifetime. As a
/// result, you should always use this alias.
pub type Cookies = self::CookieJar<'static>;
pub use cookie::{Cookie, CookieJar, Iter, CookieBuilder, Delta};
impl<'c> From<Cookie<'c>> for Header<'static> {
fn from(cookie: Cookie) -> Header<'static> {
#[derive(Debug)]
pub enum Cookies<'a> {
Jarred(RefMut<'a, CookieJar>),
Empty(CookieJar)
}
impl<'a> From<RefMut<'a, CookieJar>> for Cookies<'a> {
fn from(jar: RefMut<'a, CookieJar>) -> Cookies<'a> {
Cookies::Jarred(jar)
}
}
impl<'a> Cookies<'a> {
pub(crate) fn empty() -> Cookies<'static> {
Cookies::Empty(CookieJar::new())
}
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
match *self {
Cookies::Jarred(ref jar) => jar.get(name),
Cookies::Empty(_) => None
}
}
pub fn add(&mut self, cookie: Cookie<'static>) {
if let Cookies::Jarred(ref mut jar) = *self {
jar.add(cookie)
}
}
pub fn remove(&mut self, cookie: Cookie<'static>) {
if let Cookies::Jarred(ref mut jar) = *self {
jar.remove(cookie)
}
}
pub fn iter(&self) -> Iter {
match *self {
Cookies::Jarred(ref jar) => jar.iter(),
Cookies::Empty(ref jar) => jar.iter()
}
}
pub fn delta(&self) -> Delta {
match *self {
Cookies::Jarred(ref jar) => jar.delta(),
Cookies::Empty(ref jar) => jar.delta()
}
}
}
impl<'a, 'c> From<&'a Cookie<'c>> for Header<'static> {
fn from(cookie: &Cookie) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}

View File

@ -340,10 +340,10 @@ impl<'h> HeaderMap<'h> {
///
/// let mut map = HeaderMap::new();
///
/// map.add(Cookie::new("a", "b"));
/// map.add(&Cookie::new("a", "b"));
/// assert_eq!(map.get("Set-Cookie").count(), 1);
///
/// map.add(Cookie::new("c", "d"));
/// map.add(&Cookie::new("c", "d"));
/// assert_eq!(map.get("Set-Cookie").count(), 2);
/// ```
#[inline(always)]

View File

@ -104,6 +104,7 @@ extern crate state;
extern crate cookie;
extern crate time;
extern crate memchr;
extern crate base64;
#[cfg(test)] #[macro_use] extern crate lazy_static;

View File

@ -204,7 +204,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Method {
}
}
impl<'a, 'r> FromRequest<'a, 'r> for &'a Cookies {
impl<'a, 'r> FromRequest<'a, 'r> for Cookies<'a> {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {

View File

@ -14,6 +14,8 @@ use router::Route;
use http::uri::{URI, Segments};
use http::{Method, ContentType, Header, HeaderMap, Cookie, Cookies};
use http::CookieJar;
use http::hyper;
/// The type of an incoming web request.
@ -29,7 +31,7 @@ pub struct Request<'r> {
headers: HeaderMap<'r>,
remote: Option<SocketAddr>,
params: RefCell<Vec<(usize, usize)>>,
cookies: Cookies,
cookies: RefCell<CookieJar>,
state: Option<&'r Container>,
}
@ -54,7 +56,7 @@ impl<'r> Request<'r> {
headers: HeaderMap::new(),
remote: None,
params: RefCell::new(Vec::new()),
cookies: Cookies::new(&[]),
cookies: RefCell::new(CookieJar::new()),
state: None
}
}
@ -251,15 +253,24 @@ impl<'r> Request<'r> {
/// request.cookies().add(Cookie::new("key", "val"));
/// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4)));
/// ```
#[inline(always)]
pub fn cookies(&self) -> &Cookies {
&self.cookies
#[inline]
pub fn cookies(&self) -> Cookies {
match self.cookies.try_borrow_mut() {
Ok(jar) => Cookies::from(jar),
Err(_) => {
error_!("Multiple `Cookies` instances are active at once.");
info_!("An instance of `Cookies` must be dropped before another \
can be retrieved.");
warn_!("The retrieved `Cookies` instance will be empty.");
Cookies::empty()
}
}
}
/// Replace all of the cookies in `self` with `cookies`.
#[inline]
pub(crate) fn set_cookies(&mut self, cookies: Cookies) {
self.cookies = cookies;
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
self.cookies = RefCell::new(jar);
}
/// Returns `Some` of the Content-Type header of `self`. If the header is
@ -419,7 +430,7 @@ impl<'r> Request<'r> {
// Set the request cookies, if they exist. TODO: Use session key.
if let Some(cookie_headers) = h_headers.get_raw("Cookie") {
let mut cookies = Cookies::new(&[]);
let mut jar = CookieJar::new();
for header in cookie_headers {
let raw_str = match ::std::str::from_utf8(header) {
Ok(string) => string,
@ -432,11 +443,11 @@ impl<'r> Request<'r> {
Err(_) => continue
};
cookies.add_original(cookie);
jar.add_original(cookie);
}
}
request.set_cookies(cookies);
request.set_cookies(jar);
}
// Set the rest of the headers.

View File

@ -1,6 +1,6 @@
use std::convert::AsRef;
use time::{self, Duration};
use time::Duration;
use outcome::IntoOutcome;
use response::{Response, Responder};
@ -184,7 +184,7 @@ impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
trace_!("Flash: setting message: {}:{}", self.name, self.message);
let cookie = self.cookie();
Response::build_from(self.responder.respond()?)
.header_adjoin(cookie)
.header_adjoin(&cookie)
.ok()
}
}
@ -220,18 +220,12 @@ impl<'a, 'r> FromRequest<'a, 'r> for Flash<()> {
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
trace_!("Flash: attemping to retrieve message.");
let r = request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
let r = request.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
trace_!("Flash: retrieving message: {:?}", cookie);
// Create the "deletion" cookie. We'll use it to clear the cookie.
let delete_cookie = Cookie::build(FLASH_COOKIE_NAME, "")
.max_age(Duration::seconds(0))
.expires(time::now() - Duration::days(365))
.path("/")
.finish();
// Add the deletion to the cookie jar, replacing the existing cookie.
request.cookies().add(delete_cookie);
// Delete the flash cookie from the jar.
let orig_cookie = Cookie::build(FLASH_COOKIE_NAME, "").path("/").finish();
request.cookies().remove(orig_cookie);
// Parse the flash message.
let content = cookie.value();