1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/* Copyright 2016 Joshua Gentry
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */
use rand;

//*************************************************************************************************
/// The default session timeout in seconds.
const DEFAULT_TIMEOUT : u64 = 15*60;

//*************************************************************************************************
/// The default frequency to look for idle sessions.
const DEFAULT_FREQUENCY : u64 = 2*60;

//*************************************************************************************************
/// The number of random bytes to use for signing/encrypting the session cookie.
const DEFAULT_KEY_LEN : usize = 32;

//*************************************************************************************************
/// The default cookie name for the session.
const DEFAULT_COOKIE : &'static str = "feSession";

//*************************************************************************************************
/// Configuration object for the iron-session module.
///
/// # Examples
///
/// ```
/// use fe_session::FeSessionConfig;
///
/// let mut config = FeSessionConfig::new();
///
/// // Change the session timeout from the default of 15 minutes
/// // to 30 minutes and check for idle connections every 5 minutes.
/// config.set_session_timeout(30 * 60, 5 * 60);
///
/// // Change the session cookie from the default of "rsSession"
/// // to "mySession".
/// config.set_cookie_name("mySession".to_string());
///
/// // Change the encryption code for the session cookie from a
/// // random value to a known value.
/// config.set_cookie_key(vec![0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
/// ```
#[derive(Debug, Clone)]
pub struct FeSessionConfig
{
    //---------------------------------------------------------------------------------------------
    /// How long in seconds to keep idle sessions.
    timeout : u64,

    //---------------------------------------------------------------------------------------------
    /// The cookie to store the session key under.
    cookie_name : String,

    //---------------------------------------------------------------------------------------------
    /// The key to encrypt/sign the cookies with.
    cookie_key : Vec<u8>,

    //---------------------------------------------------------------------------------------------
    /// How often in seconds to keep check for expired sessions.
    idle_frequency : u64
}

impl FeSessionConfig
{
    //*********************************************************************************************
    /// Creates a new instance with default values.
    ///
    /// The default session time out is 15 minutes, which checks running every 2 minutes.  The
    /// default session cookie is "feSession".  And the key to sign/encrypt the cookie is randomly
    /// generated.
    pub fn new() -> FeSessionConfig
    {
        let mut key = Vec::with_capacity(DEFAULT_KEY_LEN);

        for _ in 0..32
        {
            key.push(rand::random());
        }

        FeSessionConfig {
            timeout        : DEFAULT_TIMEOUT,
            cookie_name    : DEFAULT_COOKIE.to_string(),
            cookie_key     : key,
            idle_frequency : DEFAULT_FREQUENCY
        }
    }

    //**********************************************************************************************
    /// Sets the amount of time a session can be idle before it's deleted.
    ///
    /// The timeout must be greater than 0, it's default value is 900 seconds or 15 minutes. The
    /// frequency must be less than the session timeout.
    pub fn set_session_timeout(
        &mut self,
        timeout : u64,
        frequency : u64
        )
    {
        if timeout == 0 || timeout >= 0x8000000000000000
        {
            panic!("Timeout must be greater than 0 and less than 0x8000000000000000.");
        }
        if frequency == 0 || frequency >= timeout
        {
            panic!("Frequency must be greater than 0 and less than the timeout.");
        }

        self.timeout        = timeout;
        self.idle_frequency = frequency;
    }

    //*********************************************************************************************
    /// Sets the name of the cookie to save the session ID in.
    ///
    /// The value is trimmed and the result must have a length greater than 0.  There are
    /// additional requirements that are not checked for, you may want to read RFC 2965 for what
    /// characters are valid in a cookie name.
    pub fn set_cookie_name(
        &mut self,
        name : String
        )
    {
        let trim = name.trim();

        if trim.len() > 0
        {
            self.cookie_name = trim.to_string();
        }
        else
        {
            warn!("Invalid cookie name specified.  Cookie length must be greater than 0.");
        }
    }

    //*********************************************************************************************
    /// Sets the key to encrypt/sign the session keys with.
    ///
    /// This value should be not be shared with the public.  If it is then people could in theory
    /// forge a session key and hijack someone's session.  Since the session key is only valid for
    /// a user's session a random value is probably best as it should be virtually unhackable.
    pub fn set_cookie_key(
        &mut self,
        key : Vec<u8>
        )
    {
        self.cookie_key = key;
    }

    //*********************************************************************************************
    /// Returns the session timeout in seconds.
    pub fn session_timeout(&self) -> u64
    {
        self.timeout
    }

    //*********************************************************************************************
    /// Returns the how often idle sessions are checked for in seconds.
    pub fn idle_frequency(&self) -> u64
    {
        self.idle_frequency
    }

    //*********************************************************************************************
    /// Returns the name of the cookie to save the session ID in.
    pub fn cookie_name(&self) -> &String
    {
        &self.cookie_name
    }

    //*********************************************************************************************
    /// Returns the key to sign/encrypt the cookie with.
    pub fn cookie_key(&self) -> &[u8]
    {
        &self.cookie_key[..]
    }
}

#[cfg(test)]
mod tests
{
	use super::FeSessionConfig;

    //*********************************************************************************************
    /// Test FeSessionConfig::new() works as expected with the default values.
	#[test]
	fn new()
	{
        let config = FeSessionConfig::new();

        assert_eq!(super::DEFAULT_COOKIE, config.cookie_name());
        assert_eq!(super::DEFAULT_KEY_LEN, config.cookie_key().len());
        assert_eq!(super::DEFAULT_TIMEOUT, config.session_timeout());
        assert_eq!(super::DEFAULT_FREQUENCY, config.idle_frequency());
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_session_timeout() works as expected.
    #[test]
    fn session_timeout()
    {
        let mut timeout = FeSessionConfig::new();
        timeout.set_session_timeout(1200, 120);

        assert_eq!(super::DEFAULT_COOKIE, timeout.cookie_name());
        assert_eq!(super::DEFAULT_KEY_LEN, timeout.cookie_key().len());
        assert_eq!(1200, timeout.session_timeout());
        assert_eq!(120, timeout.idle_frequency());
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_cookie_name() works as expected.
    #[test]
    fn cookie_name()
    {
        let mut cookie = FeSessionConfig::new();
        cookie.set_cookie_name("    abc   ".to_string());

        assert_eq!(&"abc".to_string(), cookie.cookie_name());
        assert_eq!(super::DEFAULT_KEY_LEN, cookie.cookie_key().len());
        assert_eq!(super::DEFAULT_TIMEOUT, cookie.session_timeout());
        assert_eq!(super::DEFAULT_FREQUENCY, cookie.idle_frequency());
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_cookie_key() works as expected.
    #[test]
    fn cookie_key()
    {
        let mut key = FeSessionConfig::new();
        key.set_cookie_key(vec![1, 2, 3]);

        assert_eq!(super::DEFAULT_COOKIE, key.cookie_name());
        assert_eq!(3, key.cookie_key().len());
        assert_eq!(1, key.cookie_key()[0]);
        assert_eq!(2, key.cookie_key()[1]);
        assert_eq!(3, key.cookie_key()[2]);
        assert_eq!(super::DEFAULT_TIMEOUT, key.session_timeout());
        assert_eq!(super::DEFAULT_FREQUENCY, key.idle_frequency());
    }

    //*********************************************************************************************
    /// Test that all the set functions together work as expected.
    #[test]
    fn everything()
    {
        let mut all = FeSessionConfig::new();
        all.set_session_timeout(1200, 120);
        all.set_cookie_name("    abc   ".to_string());
        all.set_cookie_key(vec![1, 2, 3]);

        assert_eq!(1200, all.session_timeout());
        assert_eq!(120, all.idle_frequency());
        assert_eq!(&"abc".to_string(), all.cookie_name());
        assert_eq!(3, all.cookie_key().len());
        assert_eq!(1, all.cookie_key()[0]);
        assert_eq!(2, all.cookie_key()[1]);
        assert_eq!(3, all.cookie_key()[2]);
	}

    //*********************************************************************************************
    /// Test FeSessionConfig::set_session_timeout() panics if the session timeout is zero.
    #[test]
    #[should_panic]
    fn invalid_timeout_zero()
    {
        let mut test = FeSessionConfig::new();

        test.set_session_timeout(0, 15);
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_session_timeout() panics with a session timeout of
    /// 0x8000000000000000.  This is currently a limitation because we have to convert the value
    /// to an i64 when passing it to the time crate.
    #[test]
    #[should_panic]
    fn invalid_timeout_huge()
    {
        let mut test = FeSessionConfig::new();

        test.set_session_timeout((1 as u64).rotate_right(1), 15);
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_session_timeout() panics if the frequence is zero.
    #[test]
    #[should_panic]
    fn invalid_frequency_zero()
    {
        let mut test = FeSessionConfig::new();

        test.set_session_timeout(30, 0);
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_session_timeout() panics with a frequence greater than the
    /// timeout.
    #[test]
    #[should_panic]
    fn invalid_requency_larger()
    {
        let mut test = FeSessionConfig::new();

        test.set_session_timeout(30, 60);
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_cookie_name() ignores a key with only spaces.
    #[test]
    fn invalid_cookie()
    {
        let mut test = FeSessionConfig::new();

        test.set_cookie_name("                    ".to_string());

        assert_eq!(super::DEFAULT_COOKIE, test.cookie_name());
    }

    //*********************************************************************************************
    /// Test FeSessionConfig::set_cookie_name() ignores an an empty cookie.
    #[test]
    fn empty_cookie()
    {
        let mut test = FeSessionConfig::new();

        test.set_cookie_name("".to_string());

        assert_eq!(super::DEFAULT_COOKIE, test.cookie_name());
    }
}