1use crate::error::SessionConfigError;
4use crate::session::{Session, SessionManager};
5use crate::SESSION_COOKIE_NAME;
6use cookie::Cookie;
7use data_encoding::BASE64;
8use iron::headers::{Cookie as IronCookie, SetCookie};
9use iron::middleware::{AroundMiddleware, Handler};
10use iron::{IronResult, Request, Response};
11use serde::de::DeserializeOwned;
12use serde::ser::Serialize;
13use std::marker::PhantomData;
14use time::{Duration, OffsetDateTime};
15use typemap;
16
17pub struct SessionHandler<V, K, S>
19where
20 V: Serialize + DeserializeOwned + 'static,
21 K: typemap::Key<Value = V>,
22 S: SessionManager<V>,
23{
24 manager: S,
25 config: SessionConfig,
26 handler: Box<dyn Handler>,
27 _key: PhantomData<K>,
28}
29
30impl<
31 V: Serialize + DeserializeOwned + 'static,
32 K: typemap::Key<Value = V>,
33 S: SessionManager<V>,
34 > SessionHandler<V, K, S>
35{
36 fn new(manager: S, config: SessionConfig, handler: Box<dyn Handler>) -> Self {
37 SessionHandler {
38 manager: manager,
39 config: config,
40 handler: handler,
41 _key: PhantomData,
42 }
43 }
44
45 fn extract_session_cookie(&self, request: &Request) -> Option<Vec<u8>> {
46 request.headers.get::<IronCookie>().and_then(|raw_cookie| {
47 raw_cookie
48 .0
49 .iter()
50 .filter_map(|c| {
51 Cookie::parse_encoded(c.clone())
52 .ok()
53 .and_then(|cookie| match cookie.name_value() {
54 (SESSION_COOKIE_NAME, value) => Some(value.to_string()),
55 _ => None,
56 })
57 .and_then(|c| BASE64.decode(c.as_bytes()).ok())
58 })
59 .collect::<Vec<Vec<u8>>>()
60 .first()
61 .map(|c| c.clone())
62 })
63 }
64}
65
66impl<
67 V: Serialize + DeserializeOwned + 'static,
68 K: typemap::Key<Value = V> + Send + Sync,
69 S: SessionManager<V> + 'static,
70 > Handler for SessionHandler<V, K, S>
71{
72 fn handle(&self, mut request: &mut Request) -> IronResult<Response> {
73 match self
75 .extract_session_cookie(&request)
76 .and_then(|c| self.manager.deserialize(&c).ok())
78 .and_then(|s| match s.expires {
79 Some(expires) if expires > OffsetDateTime::now_utc() => s.value,
80 None => s.value,
81 _ => None,
82 })
83 .take()
84 {
85 Some(value) => {
86 let _ = request.extensions.insert::<K>(value);
87 }
88 None => (),
89 }
90
91 let mut response = self.handler.handle(&mut request)?;
93
94 let expires = self
96 .config
97 .ttl_seconds
98 .map(|ttl| OffsetDateTime::now_utc() + Duration::seconds(ttl));
99 let session = Session {
100 expires: expires,
101 value: request.extensions.remove::<K>(),
102 };
103
104 let session_str = BASE64.encode(&self.manager.serialize(&session).unwrap());
106
107 let cookie = Cookie::build(SESSION_COOKIE_NAME, session_str)
108 .path(&self.config.path)
109 .http_only(true)
110 .secure(self.config.secure_cookie);
111 let cookie = (match self.config.ttl_seconds {
114 Some(ttl) => cookie.max_age(Duration::seconds(ttl)),
115 None => cookie,
116 })
117 .finish();
118
119 let mut cookies = vec![cookie.encoded().to_string()];
120
121 {
122 if let Some(set_cookie) = response.headers.get::<SetCookie>() {
123 cookies.extend(set_cookie.0.clone())
124 }
125 }
126 response.headers.set(SetCookie(cookies));
127
128 Ok(response)
129 }
130}
131
132pub struct SessionMiddleware<V, K, S>
134where
135 V: Serialize + DeserializeOwned + 'static,
136 K: typemap::Key<Value = V>,
137 S: SessionManager<V>,
138{
139 manager: S,
140 config: SessionConfig,
141 _key: PhantomData<K>,
142 _value: PhantomData<V>,
143}
144
145impl<
146 V: Serialize + DeserializeOwned + 'static,
147 K: typemap::Key<Value = V>,
148 S: SessionManager<V>,
149 > SessionMiddleware<V, K, S>
150{
151 pub fn new(manager: S, config: SessionConfig) -> Self {
153 SessionMiddleware {
154 manager: manager,
155 config: config,
156 _key: PhantomData,
157 _value: PhantomData,
158 }
159 }
160}
161
162impl<
163 V: Serialize + DeserializeOwned + 'static,
164 K: typemap::Key<Value = V>,
165 S: SessionManager<V> + 'static,
166 > AroundMiddleware for SessionMiddleware<V, K, S>
167where
168 SessionHandler<V, K, S>: Handler,
169{
170 fn around(self, handler: Box<dyn Handler>) -> Box<dyn Handler> {
171 Box::new(SessionHandler::<V, K, S>::new(
172 self.manager,
173 self.config,
174 handler,
175 ))
176 }
177}
178
179#[derive(Clone)]
181pub struct SessionConfig {
182 ttl_seconds: Option<i64>,
183 secure_cookie: bool,
184 path: String,
185}
186
187impl SessionConfig {
188 pub fn build() -> SessionConfigBuilder {
190 SessionConfigBuilder::new()
191 }
192}
193
194impl Default for SessionConfig {
195 fn default() -> Self {
196 Self {
197 ttl_seconds: None,
198 secure_cookie: true,
199 path: "/".into(),
200 }
201 }
202}
203
204#[derive(Clone)]
206pub struct SessionConfigBuilder {
207 config: SessionConfig,
208}
209
210impl SessionConfigBuilder {
211 pub fn new() -> Self {
213 SessionConfigBuilder {
214 config: SessionConfig::default(),
215 }
216 }
217
218 pub fn ttl_seconds(mut self, ttl_seconds: Option<i64>) -> Self {
220 self.config.ttl_seconds = ttl_seconds;
221 self
222 }
223
224 pub fn secure_cookie(mut self, secure_cookie: bool) -> Self {
226 self.config.secure_cookie = secure_cookie;
227 self
228 }
229
230 pub fn path<I: Into<String>>(mut self, path: I) -> Self {
232 self.config.path = path.into();
233 self
234 }
235
236 pub fn finish(self) -> Result<SessionConfig, SessionConfigError> {
238 match self.config.ttl_seconds {
239 Some(ttl) if ttl < 0 => {
240 return Err(SessionConfigError::Undefined);
241 }
242 _ => (),
243 };
244 Ok(self.config)
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::session::{
252 AesGcmSessionManager, ChaCha20Poly1305SessionManager, MultiSessionManager,
253 };
254 use iron::headers::{Cookie as IronCookie, Headers, SetCookie};
255 use iron::status;
256 use iron_test::request as mock_request;
257 use serde::{Deserialize, Serialize};
258 use typemap;
259
260 const KEY_32: [u8; 32] = *b"01234567012345670123456701234567";
261
262 #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
263 struct Data {
264 string: String,
265 }
266
267 struct DataKey {}
268
269 impl typemap::Key for DataKey {
270 type Value = Data;
271 }
272
273 fn mock_handler(request: &mut Request) -> IronResult<Response> {
274 let stat = match request.extensions.get::<DataKey>() {
275 Some(_) => status::Ok,
276 None => status::NoContent,
277 };
278
279 request.extensions.insert::<DataKey>(Data {
280 string: "wat".to_string(),
281 });
282
283 Ok(Response::with((stat, "")))
284 }
285
286 macro_rules! test_cases {
287 ($strct: ident, $md: ident) => {
288 mod $md {
289 use cookie::Cookie;
290 use iron::headers::{Cookie as IronCookie, Headers, SetCookie};
291 use iron::status;
292 use iron_test::request as mock_request;
293
294 use super::{mock_handler, Data, DataKey, KEY_32};
295 use $crate::middleware::{SessionConfig, SessionConfigBuilder, SessionHandler};
296 use $crate::session::$strct;
297
298 #[test]
299 fn no_expiry() {
300 let config = SessionConfig::default();
301 let manager = $strct::<Data>::from_key(KEY_32);
302 let middleware = SessionHandler::<Data, DataKey, $strct<Data>>::new(
303 manager,
304 config,
305 Box::new(mock_handler),
306 );
307
308 let path = "http://localhost/";
309
310 let response = mock_request::get(path, Headers::new(), &middleware)
311 .expect("request failed");
312 assert_eq!(response.status, Some(status::NoContent));
313
314 let set_cookie = response
316 .headers
317 .get::<SetCookie>()
318 .expect("no SetCookie header");
319 let cookie = Cookie::parse(set_cookie.0[0].clone()).expect("cookie not parsed");
320 let mut headers = Headers::new();
321 headers.set(IronCookie(vec![cookie.to_string()]));
322
323 let response =
325 mock_request::get(path, headers, &middleware).expect("request failed");
326 assert_eq!(response.status, Some(status::Ok));
327 }
328
329 #[test]
330 fn sessions_expire() {
331 let config = SessionConfigBuilder::new()
332 .ttl_seconds(Some(0))
333 .finish()
334 .unwrap();
335 let manager = $strct::<Data>::from_key(KEY_32);
336 let middleware = SessionHandler::<Data, DataKey, $strct<Data>>::new(
337 manager,
338 config,
339 Box::new(mock_handler),
340 );
341
342 let path = "http://localhost/";
343
344 let response = mock_request::get(path, Headers::new(), &middleware)
345 .expect("request failed");
346 assert_eq!(response.status, Some(status::NoContent));
347
348 let set_cookie = response
350 .headers
351 .get::<SetCookie>()
352 .expect("no SetCookie header");
353 let cookie = Cookie::parse(set_cookie.0[0].clone()).expect("cookie not parsed");
354 let mut headers = Headers::new();
355 headers.set(IronCookie(vec![cookie.to_string()]));
356
357 let response =
359 mock_request::get(path, headers, &middleware).expect("request failed");
360 assert_eq!(response.status, Some(status::NoContent));
361 }
362 }
363 };
364 }
365
366 test_cases!(AesGcmSessionManager, aesgcm);
367 test_cases!(ChaCha20Poly1305SessionManager, chacha20poly1305);
368
369 #[test]
370 fn multisession_and_rotation() {
371 let config = SessionConfig::default();
372
373 let manager_1 = AesGcmSessionManager::<Data>::from_key(KEY_32);
374 let manager_1_clone = AesGcmSessionManager::<Data>::from_key(KEY_32);
375 let middle_1 = SessionMiddleware::<Data, DataKey, AesGcmSessionManager<Data>>::new(
376 manager_1,
377 config.clone(),
378 );
379 let handler_1 = middle_1.around(Box::new(mock_handler));
380
381 let manager_2 = ChaCha20Poly1305SessionManager::<Data>::from_key(KEY_32);
382 let manager_2_clone = ChaCha20Poly1305SessionManager::<Data>::from_key(KEY_32);
383 let middle_2 =
384 SessionMiddleware::<Data, DataKey, ChaCha20Poly1305SessionManager<Data>>::new(
385 manager_2,
386 config.clone(),
387 );
388 let handler_2 = middle_2.around(Box::new(mock_handler));
389
390 let multi = MultiSessionManager::<Data>::new(
391 Box::new(manager_2_clone),
392 vec![Box::new(manager_1_clone)],
393 );
394 let multi_middle =
395 SessionMiddleware::<Data, DataKey, MultiSessionManager<Data>>::new(multi, config);
396 let multi_handler = multi_middle.around(Box::new(mock_handler));
397
398 let resp = mock_request::get("http://localhost/", Headers::new(), &handler_1).unwrap();
400 let set_cookie = resp
401 .headers
402 .get::<SetCookie>()
403 .expect("no SetCookie header");
404 let cookie = Cookie::parse(set_cookie.0[0].clone()).expect("cookie not parsed");
405
406 let mut headers = Headers::new();
408 headers.set(IronCookie(vec![cookie.to_string()]));
409 let resp = mock_request::get("http://localhost/", headers, &multi_handler).unwrap();
410 assert_eq!(resp.status, Some(status::Ok));
411
412 let set_cookie = resp
414 .headers
415 .get::<SetCookie>()
416 .expect("no SetCookie header");
417 let cookie = Cookie::parse(set_cookie.0[0].clone()).expect("cookie not parsed");
418
419 let mut headers = Headers::new();
421 headers.set(IronCookie(vec![cookie.to_string()]));
422 let resp = mock_request::get("http://localhost/", headers, &handler_2).unwrap();
423 assert_eq!(resp.status, Some(status::Ok));
424 }
425}