Remove 'Config::profile()'. CFG 'secret_key' field.

This commit makes the `Config.secret_key` conditionally compile on the
`secrets` feature. The net effect is simplified internal code, fewer
corner-cases, and easier to write tests.

This commit removes the `Provider::profile()` implementation of
`Config`. This means that the `Config` provider no longer sets a
profile, a likely confusing behavior. The `Config::figment()` continues
to function as before.
This commit is contained in:
Sergio Benitez 2021-03-08 18:18:15 -08:00
parent f454895023
commit 83ffe0f7bc
20 changed files with 268 additions and 280 deletions

View File

@ -52,9 +52,5 @@ optional = true
default-features = false
features = ["std"]
[dev-dependencies]
rocket = { version = "0.5.0-dev", path = "../lib" }
[build-dependencies]
version_check = "0.9"

View File

@ -1,5 +0,0 @@
fn main() {
if let Some(true) = version_check::is_feature_flaggable() {
println!("cargo:rustc-cfg=nightly");
}
}

View File

@ -627,6 +627,18 @@ impl<'h> HeaderMap<'h> {
}
}
impl From<cookie::Cookie<'_>> for Header<'static> {
fn from(cookie: cookie::Cookie<'_>) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
impl From<&cookie::Cookie<'_>> for Header<'static> {
fn from(cookie: &cookie::Cookie<'_>) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
#[cfg(test)]
mod tests {
use super::HeaderMap;

View File

@ -1,7 +1,5 @@
#![recursion_limit="512"]
#![cfg_attr(nightly, feature(doc_cfg))]
#![warn(rust_2018_idioms)]
#![warn(missing_docs)]
@ -23,13 +21,8 @@ pub mod ext;
#[macro_use]
mod docify;
#[doc(hidden)]
#[cfg(feature = "tls")]
pub mod tls;
#[macro_use]
mod header;
mod cookies;
mod method;
mod status;
mod raw_str;
@ -45,22 +38,20 @@ pub mod uncased {
#[doc(inline)] pub use uncased::*;
}
// Types that we expose for use by core.
// Types that we expose for use _only_ by core. Please don't use this.
#[doc(hidden)]
#[path = "."]
pub mod private {
#[cfg(feature = "tls")]
pub mod tls;
pub use crate::parse::Indexed;
pub use smallvec::{SmallVec, Array};
pub mod cookie {
pub use cookie::*;
pub use crate::cookies::Key;
}
pub use crate::listener::{Incoming, Listener, Connection, bind_tcp};
pub use cookie;
}
pub use crate::method::Method;
pub use crate::status::{Status, StatusClass};
pub use crate::raw_str::RawStr;
pub use crate::cookies::{Cookie, CookieJar, SameSite};
pub use crate::header::*;

View File

@ -7,10 +7,13 @@ use figment::value::{Map, Dict};
use serde::{Deserialize, Serialize};
use yansi::Paint;
use crate::config::{SecretKey, TlsConfig, LogLevel};
use crate::config::{TlsConfig, LogLevel};
use crate::request::{self, Request, FromRequest};
use crate::data::Limits;
#[cfg(feature = "secrets")]
use crate::config::SecretKey;
/// Rocket server configuration.
///
/// See the [module level docs](crate::config) as well as the [configuration
@ -35,13 +38,7 @@ use crate::data::Limits;
///
/// * **Profile**
///
/// The selected profile is the value of the `ROCKET_PROFILE` environment
/// variable. If the environment variable is not set, the profile is
/// selected based on whether compilation is in debug mode, where "debug" is
/// selected, or release mode, where "release" is selected.
/// [`Config::DEBUG_PROFILE`] and [`Config::RELEASE_PROFILE`] encode these
/// values as constants, while [`Config::DEFAULT_PROFILE`] selects the
/// appropriate of the two at compile-time.
/// This provider does not set a profile.
///
/// * **Metadata**
///
@ -53,6 +50,8 @@ use crate::data::Limits;
/// The data emitted by this provider are the keys and values corresponding
/// to the fields and values of the structure. The dictionary is emitted to
/// the "default" meta-profile.
///
/// Note that these behaviors differ from those of [`Config::figment()`].
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Config {
/// IP address to serve on. **(default: `127.0.0.1`)**
@ -68,6 +67,12 @@ pub struct Config {
/// The TLS configuration, if any. **(default: `None`)**
pub tls: Option<TlsConfig>,
/// The secret key for signing and encrypting. **(default: `0`)**
///
/// **Note:** This field _always_ serializes as a 256-bit array of `0`s to
/// aid in preventing leakage of the secret key.
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
#[serde(serialize_with = "SecretKey::serialize_zero")]
pub secret_key: SecretKey,
/// The directory to store temporary files in. **(default:
/// [`std::env::temp_dir`]).
@ -83,8 +88,8 @@ pub struct Config {
}
impl Default for Config {
/// Returns the default configuration based on the compilation profile. This
/// is [`Config::debug_default()`] in `debug` and
/// Returns the default configuration based on the Rust compilation profile.
/// This is [`Config::debug_default()`] in `debug` and
/// [`Config::release_default()`] in `release`.
///
/// # Example
@ -123,10 +128,13 @@ impl Config {
("dev", Some("debug")), ("prod", Some("release")),
];
/// Returns the default configuration for the `debug` profile, irrespective
/// of the compilation profile. For the default Rocket will use, which is
/// chosen based on the configuration profile, call [`Config::default()`].
/// See [Defaults](#Defaults) for specifics.
/// Returns the default configuration for the `debug` profile, _irrespective
/// of the Rust compilation profile_ and `ROCKET_PROFILE`.
///
/// This may differ from the configuration used by default,
/// [`Config::default()`], which is selected based on the Rust compilation
/// profile. See [defaults](#defaults) and [provider
/// details](#provider-details) for specifics.
///
/// # Example
///
@ -141,20 +149,24 @@ impl Config {
port: 8000,
workers: num_cpus::get(),
keep_alive: 5,
limits: Limits::default(),
tls: None,
#[cfg(feature = "secrets")]
secret_key: SecretKey::zero(),
temp_dir: std::env::temp_dir(),
log_level: LogLevel::Normal,
cli_colors: true,
secret_key: SecretKey::zero(),
tls: None,
limits: Limits::default(),
temp_dir: std::env::temp_dir(),
ctrlc: true,
}
}
/// Returns the default configuration for the `release` profile,
/// irrespective of the compilation profile. For the default Rocket will
/// use, which is chosen based on the configuration profile, call
/// [`Config::default()`]. See [Defaults](#Defaults) for specifics.
/// _irrespective of the Rust compilation profile_ and `ROCKET_PROFILE`.
///
/// This may differ from the configuration used by default,
/// [`Config::default()`], which is selected based on the Rust compilation
/// profile. See [defaults](#defaults) and [provider
/// details](#provider-details) for specifics.
///
/// # Example
///
@ -200,6 +212,7 @@ impl Config {
/// ```
pub fn figment() -> Figment {
Figment::from(Config::default())
.select(Profile::from_env_or("ROCKET_PROFILE", Self::DEFAULT_PROFILE))
.merge(Toml::file(Env::var_or("ROCKET_CONFIG", "Rocket.toml")).nested())
.merge(Env::prefixed("ROCKET_").ignore(&["PROFILE"]).global())
}
@ -224,6 +237,7 @@ impl Config {
///
/// let config = rocket::Config::from(figment);
/// ```
#[track_caller]
pub fn from<T: Provider>(provider: T) -> Self {
let figment = Figment::from(&provider);
@ -235,15 +249,17 @@ impl Config {
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))]
if !config.secret_key.is_provided() {
if figment.profile() != Self::DEBUG_PROFILE {
if figment.profile() == Self::DEBUG_PROFILE {
// in debug, try to generate a key for a bit more security
let key = SecretKey::generate().unwrap_or(SecretKey::zero());
config.secret_key = key;
} else {
crate::logger::try_init(LogLevel::Debug, true, false);
error!("secrets enabled in non-`debug` without `secret_key`");
error!("secrets enabled in non-debug without `secret_key`");
info_!("selected profile: {}", Paint::white(figment.profile()));
info_!("disable `secrets` feature or configure a `secret_key`");
panic!("aborting due to configuration error(s)")
}
// in debug, generate a key for a bit more security
config.secret_key = SecretKey::generate().unwrap_or(SecretKey::zero());
}
config
@ -290,13 +306,16 @@ impl Config {
false => launch_info_!("tls: {}", Paint::default("disabled").bold()),
}
launch_info_!("secret key: {:?}", Paint::default(&self.secret_key).bold());
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] {
launch_info_!("secret key: {:?}",
Paint::default(&self.secret_key).bold());
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))]
if !self.secret_key.is_provided() {
warn!("secrets enabled without a configured `secret_key`");
info_!("disable `secrets` feature or configure a `secret_key`");
info_!("this becomes a {} in non-debug profiles", Paint::red("hard error").bold());
if !self.secret_key.is_provided() {
warn!("secrets enabled without a configured `secret_key`");
info_!("disable `secrets` feature or configure a `secret_key`");
info_!("this becomes a {} in non-debug profiles",
Paint::red("hard error").bold());
}
}
launch_info_!("temp dir: {}", Paint::default(&self.temp_dir.display()).bold());
@ -339,20 +358,20 @@ impl Provider for Config {
#[track_caller]
fn data(&self) -> Result<Map<Profile, Dict>> {
#[allow(unused_mut)]
let mut map: Map<Profile, Dict> = Serialized::defaults(self).data()?;
// We need to special-case `secret_key` since its serializer zeroes.
#[cfg(feature = "secrets")]
if !self.secret_key.is_zero() {
if let Some(map) = map.get_mut(&Profile::Default) {
map.insert("secret_key".into(), self.secret_key.master().into());
map.insert("secret_key".into(), self.secret_key.key.master().into());
}
}
Ok(map)
}
fn profile(&self) -> Option<Profile> {
Some(Profile::from_env_or("ROCKET_PROFILE", Self::DEFAULT_PROFILE))
}
}
#[crate::async_trait]
@ -370,6 +389,7 @@ pub fn pretty_print_error(error: figment::Error) {
crate::logger::try_init(LogLevel::Debug, true, false);
error!("Rocket configuration extraction from provider failed.");
for e in error {
fn w<T: std::fmt::Display>(v: T) -> Paint<T> { Paint::white(v) }

View File

@ -70,13 +70,14 @@
//! An application that wants to use Rocket's defaults for [`Config`], but not
//! its configuration sources, while allowing the application to be configured
//! via an `App.toml` file that uses top-level keys as profiles (`.nested()`)
//! and `APP_` environment variables as global overrides (`.global()`), can be
//! structured as follows:
//! and `APP_` environment variables as global overrides (`.global()`), and
//! `APP_PROFILE` to configure the selected profile, can be structured as
//! follows:
//!
//! ```rust
//! # #[macro_use] extern crate rocket;
//! use serde::{Serialize, Deserialize};
//! use figment::{Figment, providers::{Format, Toml, Serialized, Env}};
//! use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
//! use rocket::fairing::AdHoc;
//!
//! #[derive(Debug, Deserialize, Serialize)]
@ -96,7 +97,8 @@
//! let figment = Figment::from(rocket::Config::default())
//! .merge(Serialized::defaults(Config::default()))
//! .merge(Toml::file("App.toml").nested())
//! .merge(Env::prefixed("APP_").global());
//! .merge(Env::prefixed("APP_").global())
//! .select(Profile::from_env_or("APP_PROFILE", "default"));
//!
//! rocket::custom(figment)
//! .mount("/", routes![/* .. */])
@ -109,17 +111,22 @@
//! [`Toml`]: figment::providers::Toml
//! [`Env`]: figment::providers::Env
mod secret_key;
mod config;
mod tls;
#[cfg(feature = "secrets")]
mod secret_key;
#[doc(hidden)] pub use config::pretty_print_error;
pub use config::Config;
pub use crate::logger::LogLevel;
pub use secret_key::SecretKey;
pub use tls::TlsConfig;
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub use secret_key::SecretKey;
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
@ -132,26 +139,21 @@ mod tests {
#[test]
fn test_default_round_trip() {
figment::Jail::expect_with(|_| {
let figment = Figment::from(Config::default());
let original = Config::figment();
let profile = original.profile().clone();
let roundtrip = Figment::from(Config::from(&original)).select(profile);
for figment in &[original, roundtrip] {
assert_eq!(figment.profile(), Config::DEFAULT_PROFILE);
assert_eq!(figment.profile(), Config::DEFAULT_PROFILE);
#[cfg(debug_assertions)] assert_eq!(figment.profile(), Config::DEBUG_PROFILE);
#[cfg(not(debug_assertions))] assert_eq!(figment.profile(), Config::RELEASE_PROFILE);
#[cfg(debug_assertions)]
assert_eq!(figment.profile(), Config::DEBUG_PROFILE);
let config: Config = figment.extract().unwrap();
assert_eq!(config, Config::default());
#[cfg(not(debug_assertions))]
assert_eq!(figment.profile(), Config::RELEASE_PROFILE);
let config: Config = figment.extract().unwrap();
assert_eq!(config, Config::default());
#[cfg(debug_assertions)]
assert_eq!(config, Config::debug_default());
#[cfg(not(debug_assertions))]
assert_eq!(config, Config::release_default());
assert_eq!(Config::from(Config::default()), Config::default());
#[cfg(debug_assertions)] assert_eq!(config, Config::debug_default());
#[cfg(not(debug_assertions))] assert_eq!(config, Config::release_default());
}
Ok(())
});
@ -161,15 +163,15 @@ mod tests {
fn test_profile_env() {
figment::Jail::expect_with(|jail| {
jail.set_env("ROCKET_PROFILE", "debug");
let figment = Figment::from(Config::default());
let figment = Config::figment();
assert_eq!(figment.profile(), "debug");
jail.set_env("ROCKET_PROFILE", "release");
let figment = Figment::from(Config::default());
let figment = Config::figment();
assert_eq!(figment.profile(), "release");
jail.set_env("ROCKET_PROFILE", "random");
let figment = Figment::from(Config::default());
let figment = Config::figment();
assert_eq!(figment.profile(), "random");
Ok(())

View File

@ -1,5 +1,4 @@
use std::fmt;
use std::ops::Deref;
use serde::{de, ser, Deserialize, Serialize};
@ -18,36 +17,54 @@ enum Kind {
/// A `SecretKey` is primarily used by [private cookies]. See the [configuration
/// guide] for further details. It can be configured from 256-bit random
/// material or a 512-bit master key, each as either a base64-encoded string or
/// raw bytes. When compiled in debug mode with the `secrets` feature enabled, a
/// raw bytes.
///
/// ```rust
/// use rocket::config::Config;
///
/// let figment = Config::figment()
/// .merge(("secret_key", "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="));
///
/// let config = Config::from(figment);
/// assert!(!config.secret_key.is_zero());
/// ```
///
/// When configured in the debug profile with the `secrets` feature enabled, a
/// key set a `0` is automatically regenerated from the OS's random source if
/// available.
///
/// ```rust
/// # use rocket::figment::Figment;
/// let figment = Figment::from(rocket::Config::default())
/// .merge(("secret_key", "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="));
/// ```rust,ignore
/// # // FIXME: Don't special case `SecretKey`.
/// use rocket::config::Config;
///
/// # #[cfg(feature = "secrets")]
/// assert!(!rocket::Config::from(figment).secret_key.is_zero());
/// # #[cfg(not(feature = "secrets"))]
/// # assert!(rocket::Config::from(figment).secret_key.is_zero());
/// let figment = Config::figment()
/// .merge(("secret_key", vec![0u8; 64]))
/// .select("debug");
///
/// let figment = Figment::from(rocket::Config::default())
/// .merge(("secret_key", vec![0u8; 64]));
/// let config = Config::from(figment);
/// assert!(!config.secret_key.is_zero());
/// ```
///
/// # /* as far as I can tell, there's no way to test this properly
/// # https://github.com/rust-lang/cargo/issues/6570
/// # https://github.com/rust-lang/cargo/issues/4737
/// # https://github.com/rust-lang/rust/issues/43031
/// assert!(!rocket::Config::from(figment).secret_key.is_zero());
/// # */
/// When running in any other profile with the `secrets` feature enabled,
/// providing a key of `0` or not provided a key at all results in a failure at
/// launch-time:
///
/// ```rust,should_panic,ignore
/// # // FIXME: Don't special case `SecretKey` checking on test/unsafe_key.
/// use rocket::config::Config;
///
/// let figment = Config::figment()
/// .merge(("secret_key", vec![0u8; 64]))
/// .select("staging");
///
/// let config = Config::from(figment);
/// ```
///
/// [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies
/// [configuration guide]: https://rocket.rs/master/guide/configuration/#secret-key
#[derive(Clone)]
pub struct SecretKey {
key: Key,
pub(crate) key: Key,
provided: bool,
}
@ -142,14 +159,12 @@ impl SecretKey {
pub fn is_provided(&self) -> bool {
self.provided && !self.is_zero()
}
}
#[doc(hidden)]
impl Deref for SecretKey {
type Target = Key;
fn deref(&self) -> &Self::Target {
&self.key
/// Serialize as `zero` to avoid key leakage.
pub(crate) fn serialize_zero<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where S: ser::Serializer
{
ser.serialize_bytes(&[0; 32][..])
}
}
@ -169,13 +184,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'a SecretKey {
}
}
impl Serialize for SecretKey {
fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
// We encode as "zero" to avoid leaking the key.
ser.serialize_bytes(&[0; 32][..])
}
}
impl<'de> Deserialize<'de> for SecretKey {
fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
use {binascii::{b64decode, hex2bin}, de::Unexpected::Str};

View File

@ -10,8 +10,9 @@ use serde::{Deserialize, Serialize};
/// The following example illustrates manual configuration:
///
/// ```rust
/// # use rocket::figment::Figment;
/// let figment = Figment::from(rocket::Config::default())
/// use rocket::Config;
///
/// let figment = rocket::Config::figment()
/// .merge(("tls.certs", "strings/are/paths/certs.pem"))
/// .merge(("tls.key", vec![0; 32]));
///
@ -79,8 +80,9 @@ impl TlsConfig {
/// # Example
///
/// ```rust
/// # use rocket::figment::Figment;
/// let figment = Figment::from(rocket::Config::default())
/// use rocket::Config;
///
/// let figment = Config::figment()
/// .merge(("tls.certs", vec![0; 32]))
/// .merge(("tls.key", "/etc/ssl/key.pem"));
///
@ -101,9 +103,10 @@ impl TlsConfig {
/// # Example
///
/// ```rust
/// # use rocket::figment::Figment;
/// # use std::path::Path;
/// let figment = Figment::from(rocket::Config::default())
/// use std::path::Path;
/// use rocket::Config;
///
/// let figment = Config::figment()
/// .merge(("tls.certs", vec![0; 32]))
/// .merge(("tls.key", "/etc/ssl/key.pem"));
///

View File

@ -2,41 +2,11 @@ use std::fmt;
use parking_lot::Mutex;
use crate::Header;
use crate::Config;
use crate::http::private::cookie;
pub use cookie::{Cookie, SameSite, Iter};
#[doc(hidden)] pub use self::key::*;
/// Types and methods to manage a `Key` when private cookies are enabled.
#[cfg(feature = "private-cookies")]
mod key {
pub use cookie::Key;
}
/// Types and methods to manage a `Key` when private cookies are disabled.
#[cfg(not(feature = "private-cookies"))]
#[allow(missing_docs)]
mod key {
#[derive(Copy, Clone)]
pub struct Key;
impl Key {
pub fn from(_: &[u8]) -> Self { Key }
pub fn derive_from(_: &[u8]) -> Self { Key }
pub fn generate() -> Self { Key }
pub fn try_generate() -> Option<Self> { Some(Key) }
pub fn master(&self) -> &[u8] {
static ZERO: &'static [u8; 64] = &[0; 64];
&ZERO[..]
}
}
impl PartialEq for Key {
fn eq(&self, _: &Self) -> bool {
true
}
}
}
#[doc(inline)]
pub use self::cookie::{Cookie, SameSite, Iter};
/// Collection of one or more HTTP cookies.
///
@ -133,7 +103,7 @@ mod key {
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # #[cfg(feature = "private-cookies")] {
/// # #[cfg(feature = "secrets")] {
/// use rocket::http::Status;
/// use rocket::outcome::IntoOutcome;
/// use rocket::request::{self, Request, FromRequest};
@ -187,16 +157,16 @@ mod key {
/// 32`.
pub struct CookieJar<'a> {
jar: cookie::CookieJar,
key: &'a Key,
ops: Mutex<Vec<Op>>,
config: &'a Config,
}
impl<'a> Clone for CookieJar<'a> {
fn clone(&self) -> Self {
CookieJar {
jar: self.jar.clone(),
key: self.key,
ops: Mutex::new(self.ops.lock().clone()),
config: self.config,
}
}
}
@ -216,6 +186,15 @@ impl Op {
}
impl<'a> CookieJar<'a> {
#[inline(always)]
pub(crate) fn new(config: &'a Config) -> Self {
CookieJar::from(cookie::CookieJar::new(), config)
}
pub(crate) fn from(jar: cookie::CookieJar, config: &'a Config) -> Self {
CookieJar { jar, config, ops: Mutex::new(Vec::new()) }
}
/// Returns a reference to the _original_ `Cookie` inside this container
/// with the name `name`. If no such cookie exists, returns `None`.
///
@ -258,10 +237,10 @@ impl<'a> CookieJar<'a> {
/// let cookie = jar.get_private("name");
/// }
/// ```
#[cfg(feature = "private-cookies")]
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn get_private(&self, name: &str) -> Option<Cookie<'static>> {
self.jar.private(&*self.key).get(name)
self.jar.private(&self.config.secret_key.key).get(name)
}
/// Returns a reference to the _original or pending_ `Cookie` inside this
@ -308,11 +287,11 @@ impl<'a> CookieJar<'a> {
/// let pending_cookie = jar.get_private_pending("name");
/// }
/// ```
#[cfg(feature = "private-cookies")]
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn get_private_pending(&self, name: &str) -> Option<Cookie<'static>> {
let cookie = self.get_pending(name)?;
self.jar.private(&*self.key).decrypt(cookie)
self.jar.private(&self.config.secret_key.key).decrypt(cookie)
}
/// Adds `cookie` to this collection.
@ -374,7 +353,7 @@ impl<'a> CookieJar<'a> {
/// jar.add_private(Cookie::new("name", "value"));
/// }
/// ```
#[cfg(feature = "private-cookies")]
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn add_private(&self, mut cookie: Cookie<'static>) {
Self::set_private_defaults(&mut cookie);
@ -428,7 +407,7 @@ impl<'a> CookieJar<'a> {
/// jar.remove_private(Cookie::named("name"));
/// }
/// ```
#[cfg(feature = "private-cookies")]
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn remove_private(&self, mut cookie: Cookie<'static>) {
if cookie.path().is_none() {
@ -460,42 +439,26 @@ impl<'a> CookieJar<'a> {
pub fn iter(&self) -> impl Iterator<Item=&Cookie<'static>> {
self.jar.iter()
}
}
/// WARNING: These are unstable! Do not use outside of Rocket!
#[doc(hidden)]
impl<'a> CookieJar<'a> {
#[inline(always)]
pub fn new(key: &'a Key) -> Self {
CookieJar {
jar: cookie::CookieJar::new(),
key, ops: Mutex::new(Vec::new()),
}
}
#[inline(always)]
pub fn from(jar: cookie::CookieJar, key: &'a Key) -> CookieJar<'a> {
CookieJar { jar, key, ops: Mutex::new(Vec::new()) }
}
/// Removes all delta cookies.
#[inline(always)]
pub fn reset_delta(&self) {
pub(crate) fn reset_delta(&self) {
self.ops.lock().clear();
}
/// TODO: This could be faster by just returning the cookies directly via
/// an ordered hash-set of sorts.
#[inline(always)]
pub fn take_delta_jar(&self) -> cookie::CookieJar {
pub(crate) fn take_delta_jar(&self) -> cookie::CookieJar {
let ops = std::mem::replace(&mut *self.ops.lock(), Vec::new());
let mut jar = cookie::CookieJar::new();
for op in ops {
match op {
Op::Add(c, false) => jar.add(c),
#[cfg(feature = "private-cookies")]
Op::Add(c, true) => jar.private_mut(self.key).add(c),
#[cfg(feature = "secrets")]
Op::Add(c, true) => {
jar.private_mut(&self.config.secret_key.key).add(c);
}
Op::Remove(mut c, _) => {
if self.jar.get(c.name()).is_some() {
c.make_removal();
@ -514,16 +477,16 @@ impl<'a> CookieJar<'a> {
/// Adds an original `cookie` to this collection.
#[inline(always)]
pub fn add_original(&mut self, cookie: Cookie<'static>) {
pub(crate) fn add_original(&mut self, cookie: Cookie<'static>) {
self.jar.add_original(cookie)
}
/// Adds an original, private `cookie` to the collection.
#[inline(always)]
#[cfg(feature = "private-cookies")]
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn add_original_private(&mut self, cookie: Cookie<'static>) {
self.jar.private_mut(&*self.key).add_original(cookie);
#[inline(always)]
pub(crate) fn add_original_private(&mut self, cookie: Cookie<'static>) {
self.jar.private_mut(&self.config.secret_key.key).add_original(cookie);
}
/// For each property mentioned below, this method checks if there is a
@ -552,7 +515,7 @@ impl<'a> CookieJar<'a> {
/// * `HttpOnly`: `true`
/// * `Expires`: 1 week from now
///
#[cfg(feature = "private-cookies")]
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
fn set_private_defaults(cookie: &mut Cookie<'static>) {
if cookie.path().is_none() {
@ -586,16 +549,5 @@ impl fmt::Debug for CookieJar<'_> {
.field("pending", &pending)
.finish()
}
}
impl From<Cookie<'_>> for Header<'static> {
fn from(cookie: Cookie<'_>) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
impl From<&Cookie<'_>> for Header<'static> {
fn from(cookie: &Cookie<'_>) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}

View File

@ -133,6 +133,9 @@ pub mod http {
#[doc(inline)]
pub use rocket_http::*;
#[doc(inline)]
pub use crate::cookies::*;
}
mod shutdown;
@ -142,6 +145,7 @@ mod server;
mod codegen;
mod ext;
mod state;
mod cookies;
#[doc(hidden)] pub use log::{info, warn, error, debug};
#[doc(inline)] pub use crate::response::Response;

View File

@ -89,7 +89,7 @@ impl<'c> LocalResponse<'c> {
async move {
let response: Response<'c> = f(request).await;
let mut cookies = CookieJar::new(&request.state.config.secret_key);
let mut cookies = CookieJar::new(&request.state.config);
for cookie in response.cookies() {
cookies.add_original(cookie.into_owned());
}

View File

@ -149,9 +149,9 @@ macro_rules! pub_client_impl {
/// ```
#[inline(always)]
pub fn cookies(&self) -> crate::http::CookieJar<'_> {
let key = &self.rocket().config.secret_key;
let config = &self.rocket().config;
let jar = self._with_raw_cookies(|jar| jar.clone());
crate::http::CookieJar::from(jar, key)
crate::http::CookieJar::from(jar, config)
}
req_method!($import, "GET", get, Method::Get);

View File

@ -89,7 +89,7 @@ impl<'r> Request<'r> {
managed: &rocket.managed_state,
shutdown: &rocket.shutdown_handle,
route: Atomic::new(None),
cookies: CookieJar::new(&rocket.config.secret_key),
cookies: CookieJar::new(&rocket.config),
accept: Storage::new(),
content_type: Storage::new(),
cache: Arc::new(<Container![Send + Sync]>::new()),

View File

@ -52,6 +52,7 @@ impl Rocket {
/// rocket::ignite()
/// # };
/// ```
#[track_caller]
pub fn ignite() -> Rocket {
Rocket::custom(Config::figment())
}
@ -67,19 +68,20 @@ impl Rocket {
///
/// # Examples
///
/// ```rust,no_run
/// ```rust
/// use figment::{Figment, providers::{Toml, Env, Format}};
///
/// #[rocket::launch]
/// fn rocket() -> _ {
/// let figment = Figment::from(rocket::Config::default())
/// .merge(Toml::file("MyApp.toml").nested())
/// .merge(Env::prefixed("MY_APP_"));
/// .merge(Env::prefixed("MY_APP_").global());
///
/// rocket::custom(figment)
/// }
/// ```
#[inline]
#[track_caller]
pub fn custom<T: figment::Provider>(provider: T) -> Rocket {
let (config, figment) = (Config::from(&provider), Figment::from(provider));
logger::try_init(config.log_level, config.cli_colors, false);
@ -529,7 +531,7 @@ impl Rocket {
#[cfg(feature = "tls")]
let server = {
use crate::http::tls::bind_tls;
use crate::http::private::tls::bind_tls;
if let Some(tls_config) = &self.config.tls {
let (certs, key) = tls_config.to_readers().map_err(ErrorKind::Io)?;

View File

@ -18,8 +18,7 @@ mod test_absolute_uris_okay {
#[test]
fn redirect_works() {
let rocket = rocket::ignite().mount("/", routes![google, redirect]);
let client = Client::tracked(rocket).unwrap();
let client = Client::debug("/", routes![google, redirect]).unwrap();
let response = client.get("/google").dispatch();
let location = response.headers().get_one("Location");

View File

@ -1,3 +1,5 @@
#![cfg(feature = "secrets")]
use rocket::figment::Figment;
use rocket::config::{Config, SecretKey};

View File

@ -14,8 +14,11 @@ mod limits_tests {
use rocket::data::Limits;
fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket {
let limits = Limits::default().limit("form", limit.into());
let config = rocket::Config::figment().merge(("limits", limits));
let config = rocket::Config {
limits: Limits::default().limit("form", limit.into()),
..rocket::Config::debug_default()
};
rocket::custom(config).mount("/", routes![super::index])
}

View File

@ -1,42 +1,41 @@
#[cfg(feature = "secrets")]
mod private_cookies {
use rocket::http::CookieJar;
#![cfg(feature = "secrets")]
#[rocket::get("/")]
fn return_private_cookie(cookies: &CookieJar<'_>) -> Option<String> {
match cookies.get_private("cookie_name") {
Some(cookie) => Some(cookie.value().into()),
None => None,
}
}
use rocket::http::CookieJar;
mod tests {
use super::*;
use rocket::routes;
use rocket::local::blocking::Client;
use rocket::http::{Cookie, Status};
#[test]
fn private_cookie_is_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::tracked(rocket).unwrap();
let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch();
assert_eq!(response.headers().get_one("Set-Cookie"), None);
assert_eq!(response.into_string(), Some("cookie_value".into()));
}
#[test]
fn regular_cookie_is_not_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::tracked(rocket).unwrap();
let req = client.get("/").cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch();
assert_eq!(response.status(), Status::NotFound);
}
#[rocket::get("/")]
fn return_private_cookie(cookies: &CookieJar<'_>) -> Option<String> {
match cookies.get_private("cookie_name") {
Some(cookie) => Some(cookie.value().into()),
None => None,
}
}
mod tests {
use super::*;
use rocket::routes;
use rocket::local::blocking::Client;
use rocket::http::{Cookie, Status};
#[test]
fn private_cookie_is_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::tracked(rocket).unwrap();
let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch();
assert_eq!(response.headers().get_one("Set-Cookie"), None);
assert_eq!(response.into_string(), Some("cookie_value".into()));
}
#[test]
fn regular_cookie_is_not_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::tracked(rocket).unwrap();
let req = client.get("/").cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch();
assert_eq!(response.status(), Status::NotFound);
}
}

View File

@ -1,25 +1,24 @@
#[cfg(feature = "secrets")]
mod with_secrets {
use rocket::http::{CookieJar, Cookie};
#![cfg(feature = "secrets")]
#[rocket::get("/")]
fn index(jar: &CookieJar<'_>) {
let session_cookie = Cookie::build("key", "value").expires(None);
jar.add_private(session_cookie.finish());
}
use rocket::http::{CookieJar, Cookie};
mod test_session_cookies {
use super::*;
use rocket::local::blocking::Client;
#[rocket::get("/")]
fn index(jar: &CookieJar<'_>) {
let session_cookie = Cookie::build("key", "value").expires(None);
jar.add_private(session_cookie.finish());
}
#[test]
fn session_cookie_is_session() {
let rocket = rocket::ignite().mount("/", rocket::routes![index]);
let client = Client::tracked(rocket).unwrap();
mod test_session_cookies {
use super::*;
use rocket::local::blocking::Client;
let response = client.get("/").dispatch();
let cookie = response.cookies().get_private("key").unwrap();
assert_eq!(cookie.expires_datetime(), None);
}
#[test]
fn session_cookie_is_session() {
let rocket = rocket::ignite().mount("/", rocket::routes![index]);
let client = Client::tracked(rocket).unwrap();
let response = client.get("/").dispatch();
let cookie = response.cookies().get_private("key").unwrap();
assert_eq!(cookie.expires_datetime(), None);
}
}

View File

@ -314,14 +314,14 @@ fn rocket() -> _ {
More involved, consider an application that wants to use Rocket's defaults for
[`Config`], but not its configuration sources, while allowing the application to
be configured via an `App.toml` file that uses top-level keys as profiles
(`.nested()`) and `APP_` environment variables as global overrides
(`.global()`):
(`.nested()`), `APP_` environment variables as global overrides (`.global()`),
and `APP_PROFILE` to configure the selected profile:
```rust
# #[macro_use] extern crate rocket;
use serde::{Serialize, Deserialize};
use figment::{Figment, providers::{Format, Toml, Serialized, Env}};
use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
use rocket::fairing::AdHoc;
#[derive(Debug, Deserialize, Serialize)]
@ -341,7 +341,8 @@ fn rocket() -> _ {
let figment = Figment::from(rocket::Config::default())
.merge(Serialized::defaults(Config::default()))
.merge(Toml::file("App.toml").nested())
.merge(Env::prefixed("APP_").global());
.merge(Env::prefixed("APP_").global())
.select(Profile::from_env_or("APP_PROFILE", "default"));
rocket::custom(figment)
.mount("/", routes![/* .. */])