frikandel 1.0.0 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5d21550e8f406f5cfadd7246e3a5b511e6719b2d
4
- data.tar.gz: c205899f482cc2e2e6fc64f69070c7b5f4cdda59
2
+ SHA256:
3
+ metadata.gz: 1d7333c87689c365122f6199098938f9af94a4956de807b0f7d4d00109598885
4
+ data.tar.gz: bbc4c5a50c8d122d99747475d29b6c910503793ef81f6357aade4bde6bf44b71
5
5
  SHA512:
6
- metadata.gz: 40977fc969914d45e38951d1bcd37577bb8376f8e8c9e13dbf56d2076194973409de535136ee6af30e3c31b5ab34f52b1350e630cc6723c179b0c131f8288b0b
7
- data.tar.gz: 62eb2c7883e57b57c297db621bc5eccf42ab4f653a93ff7e7867fa7bc57d386c7765fd11914cdb5f954085c1aea8842d1a27dc33346598b30ebdd7b79334630c
6
+ metadata.gz: 1fa2905e7460cc9dc79e28bc79dbc2aa05bce516b432b8bd4e6cff9e2d469e5f23c073955ce53aaa47c28c25eb5d6d3bfd014d2efc164dd5159417fbbbd5cc12
7
+ data.tar.gz: b6a744f3c6deecb734addc0e292f0d42c7b7274d8d9d708aeddfa40091a5ab4de173caeccbd678cd02a08b7c84f2448809c38eb7229b3e7968c7023d15dc34d4
data/.gitignore CHANGED
@@ -4,7 +4,7 @@
4
4
  .config
5
5
  .ruby-*
6
6
  .yardoc
7
- Gemfile.lock
7
+ Gemfile*.lock
8
8
  InstalledFiles
9
9
  _yardoc
10
10
  coverage
@@ -1,7 +1,45 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.0
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - "2.1.9"
6
+ - "2.2.5"
7
+ - "2.3.1"
8
+ - ruby-head
6
9
  - jruby-19mode
7
-
10
+ gemfile:
11
+ - Gemfile.rails-3.2.x
12
+ - Gemfile.rails-4.0.x
13
+ - Gemfile.rails-4.1.x
14
+ - Gemfile.rails-4.2.x
15
+ - Gemfile.rails-5.0.x
16
+ - Gemfile.rails-5.1.x
17
+ - Gemfile.rails-head
18
+ before_install:
19
+ - gem update --system
20
+ - gem install bundler --pre
21
+ matrix:
22
+ allow_failures:
23
+ - rvm: ruby-head
24
+ - gemfile: Gemfile.rails-head
25
+ exclude:
26
+ - rvm: "2.2.5"
27
+ gemfile: Gemfile.rails-3.2.x
28
+ - rvm: "2.2.5"
29
+ gemfile: Gemfile.rails-4.0.x
30
+ - rvm: "1.9.3"
31
+ gemfile: Gemfile.rails-5.0.x
32
+ - rvm: "2.0.0"
33
+ gemfile: Gemfile.rails-5.0.x
34
+ - rvm: "2.1.9"
35
+ gemfile: Gemfile.rails-5.0.x
36
+ - rvm: "jruby-19mode"
37
+ gemfile: Gemfile.rails-5.0.x
38
+ - rvm: "1.9.3"
39
+ gemfile: Gemfile.rails-5.1.x
40
+ - rvm: "2.0.0"
41
+ gemfile: Gemfile.rails-5.1.x
42
+ - rvm: "2.1.9"
43
+ gemfile: Gemfile.rails-5.1.x
44
+ - rvm: "jruby-19mode"
45
+ gemfile: Gemfile.rails-5.1.x
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', '~> 3.2.0'
7
+ gem 'mime-types', '< 3.0'
8
+ gem 'listen', '< 3.1'
9
+ gem 'sqlite3', '~> 1.3.6'
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', '~> 4.0.0'
7
+ gem 'mime-types', '< 3.0'
8
+ gem 'listen', '< 3.1'
9
+ gem 'sqlite3', '~> 1.3.6'
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', '~> 4.1.0'
7
+ gem 'mime-types', '< 3.0'
8
+ gem 'listen', '< 3.1'
9
+ gem 'sqlite3', '~> 1.3.6'
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', '~> 4.2.0'
7
+ gem 'mime-types', '< 3.0'
8
+ gem 'listen', '< 3.1'
9
+ gem 'sqlite3', '~> 1.3.6'
10
+ gem 'sprockets', '~> 3.7.2'
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', '~> 5.0.0'
7
+ gem 'sqlite3', '~> 1.3.6'
8
+ gem 'sprockets', '~> 3.7.2'
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', '~> 5.1.0'
7
+ gem 'sqlite3', '~> 1.3.6'
8
+ gem 'sprockets', '~> 3.7.2'
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', '~> 5.2.0'
7
+ gem 'sqlite3', '~> 1.3.6'
8
+ gem 'sprockets', '~> 3.7.2'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frikandel.gemspec
4
+ gemspec
5
+
6
+ gem 'rails', 'github' => 'rails/rails'
data/Guardfile CHANGED
@@ -3,8 +3,7 @@
3
3
 
4
4
  guard :rspec do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
8
-
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| ["spec/lib/#{m[1]}_spec.rb", "spec/controllers"] }
7
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
9
  end
10
-
data/README.md CHANGED
@@ -1,7 +1,20 @@
1
1
  # Frikandel
2
+ [![Gem Version](https://badge.fury.io/rb/frikandel.png)](http://badge.fury.io/rb/frikandel)
3
+ [![Build Status](https://api.travis-ci.org/taktsoft/frikandel.png)](https://travis-ci.org/taktsoft/frikandel)
4
+ [![Code Climate](https://codeclimate.com/github/taktsoft/frikandel.png)](https://codeclimate.com/github/taktsoft/frikandel)
5
+ [![Dependency Status](https://gemnasium.com/taktsoft/frikandel.svg)](https://gemnasium.com/taktsoft/frikandel)
2
6
 
3
- This Gem adds a TTL (Time To Live) Date to every cookie that your application sets. When the cookie has expired, the users session gets reset. This should help protect from Session-Fixation-Attacks.
7
+ This gem aims to improve the security of your rails application. It allows you to add a TTL (Time To Live) to the session cookie and allows you to bind the session to an IP address.
4
8
 
9
+ When the TTL expires or the IP address changes, the users session gets reset. This should help to make [session-fixation-attacks](http://guides.rubyonrails.org/security.html#session-fixation) harder to execute.
10
+
11
+
12
+ ## Security considerations
13
+
14
+ Consider the following attack vector: The web application under attack is a rails application. The application writes the user id in the session after a successful login. The attacker has obtained a valid session cookie from an authenticated user.
15
+ By default the cookie is valid indefinitely. If the application tries to "reset the session" it simply issues a new session cookie to the attackers browser. If the attacker just ignores the new session cookie and continues to use the old session cookie the application has no way of knowing that.
16
+
17
+ By adding a TTL the attack window gets smaller. An stolen has to be used within a given time slot. A reauthentication is enforced after a given time has passed. By adding IP address binding the attacker has to use the same ip address as the victim the session was stolen from.
5
18
 
6
19
  ## Requirements
7
20
 
@@ -28,11 +41,15 @@ Or install it yourself as:
28
41
 
29
42
  ## Usage
30
43
 
31
- To activate frikandel's Session-Fixation-Protection for your application, you only need to include a module in your `ApplicationController`:
44
+ You can use session TTL or the combination of TTL and IP address binding. Please be advised that the sole use of IP address binding doesn't protect from session-fixation-attacks.
45
+
46
+
47
+ To activate Frikandel's session-fixation-protection for your application, you only need to include the proper module(s) in your `ApplicationController`:
32
48
 
33
49
  ```ruby
34
50
  class ApplicationController < ActionController::Base
35
51
  include Frikandel::LimitSessionLifetime
52
+ include Frikandel::BindSessionToIpAddress
36
53
 
37
54
  # ...
38
55
  end
@@ -47,33 +64,50 @@ Frikandel::Configuration.max_ttl = 2.days
47
64
  Frikandel::Configuration.ttl = 4.hours
48
65
  ```
49
66
 
50
- The value at `Frikandel::Configuration.max_ttl` is the absolute value in seconds that a cookie is valid. In this example, all cookies will be invalidated after two days in all cases. This timestamp doesn't get refreshed.
67
+ The value at `Frikandel::Configuration.max_ttl` is the absolute value (in seconds) that a cookie is valid. In this example, all cookies will be invalidated after two days in all cases. This timestamp doesn't get refreshed. In a typical application that means the user has to re-login after this time. That's also the maximum time frame a stolen session can be used.
51
68
 
52
69
  The second value `Frikandel::Configuration.ttl` states how long (in seconds) a session/cookie is valid, when the cookie timestamp gets not refreshed. The timestamp gets refrehed everytime a user visits the site.
53
70
 
54
71
  The default values are `24.hours` for `max_ttl` and `2.hours` for `ttl`. If you are okay with this settings, you don't need to create an initializer for frikandel.
55
72
 
56
73
 
57
- ### Customize on_expired_session behavior
74
+ ### Customize on_invalid_session behavior
58
75
 
59
- You can also overwrite what should happen when a cookie times out on the controller-level. The default behaviour is to do a `reset_session` and `redirect_to root_path`. For example, if you want to overwrite the default behavior when a user is on the `PublicController`, you want to overwrite the `on_expired_session`-method in your controller:
76
+ You can also overwrite what should happen when a cookie times out on the controller-level. The default behaviour is to do a `reset_session` and `redirect_to root_path`. For example, if you want to overwrite the default behavior when a user is on the `PublicController`, you want to overwrite the `on_invalid_session`-method in your controller:
60
77
 
61
78
  ```ruby
62
79
  class PublicController < ApplicationController
63
- def on_expired_session
80
+ def on_invalid_session
64
81
  raise "Your Session Has Expired! Oh No!"
65
82
  end
66
83
  end
67
84
  ```
68
85
 
69
- If you want to revert the original behavior in a sub-class of your `PublicController`, you simply re-alias the method to `original_on_expired_session` like this:
86
+ If you want to revert the original behavior in a sub-class of your `PublicController`, you simply re-alias the method to `original_on_invalid_session` like this:
70
87
 
71
88
  ```ruby
72
89
  class AdminController < PublicController
73
- alias on_expired_session original_on_expired_session
90
+ alias on_invalid_session original_on_invalid_session
74
91
  end
75
92
  ```
76
93
 
94
+ ## Changes
95
+
96
+ 2.1.0 -- Reset session only once if using the combination of TTL and IP address binding.
97
+ 2.0.0 -- Added IP address binding. Renamed callback from 'on_expired_session' to 'on_invalid_session'.
98
+
99
+ ## Test
100
+
101
+ To run the test suite with different rails version by selecting the corresponding gemfile. You can use this one liners:
102
+
103
+ $ export BUNDLE_GEMFILE=Gemfile.rails-3.2.x && bundle update && bundle exec rake spec
104
+ $ export BUNDLE_GEMFILE=Gemfile.rails-4.0.x && bundle update && bundle exec rake spec
105
+ $ export BUNDLE_GEMFILE=Gemfile.rails-4.1.x && bundle update && bundle exec rake spec
106
+ $ export BUNDLE_GEMFILE=Gemfile.rails-4.2.x && bundle update && bundle exec rake spec
107
+ $ export BUNDLE_GEMFILE=Gemfile.rails-5.0.x && bundle update && bundle exec rake spec
108
+ $ export BUNDLE_GEMFILE=Gemfile.rails-5.1.x && bundle update && bundle exec rake spec
109
+ $ export BUNDLE_GEMFILE=Gemfile.rails-5.2.x && bundle update && bundle exec rake spec
110
+
77
111
  ## Contributing
78
112
  1. Fork it
79
113
  2. Create your feature branch (git checkout -b my-new-feature)
@@ -18,14 +18,18 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = Dir["spec/**/*"]
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.required_ruby_version = '>= 1.9.3'
22
+ spec.required_rubygems_version = ">= 1.3.6"
23
+
21
24
  spec.add_development_dependency "bundler", "~> 1.5"
22
25
  spec.add_development_dependency "rake"
23
26
  spec.add_development_dependency "sqlite3" unless RUBY_PLATFORM == 'java'
24
27
  spec.add_development_dependency "jdbc-sqlite3" if RUBY_PLATFORM == 'java'
25
28
  spec.add_development_dependency "activerecord-jdbcsqlite3-adapter" if RUBY_PLATFORM == 'java'
26
- spec.add_development_dependency "rspec-rails"
29
+ spec.add_development_dependency "rspec-rails", ["> 3.0", "< 3.6"]
27
30
  spec.add_development_dependency "guard-rspec"
28
31
  spec.add_development_dependency "pry"
32
+ spec.add_development_dependency "test-unit"
29
33
 
30
- spec.add_dependency "rails", ['>= 3.2.0', '< 5.0']
34
+ spec.add_dependency "rails", [">= 3.2.0", "< 6.0"]
31
35
  end
@@ -1,32 +1,5 @@
1
1
  require "frikandel/version"
2
2
  require "frikandel/configuration"
3
-
4
- module Frikandel
5
- module LimitSessionLifetime
6
- extend ActiveSupport::Concern
7
-
8
- included do
9
- append_before_filter :validate_session_timestamp
10
- append_after_filter :persist_session_timestamp
11
- end
12
-
13
- private
14
-
15
- def validate_session_timestamp
16
- if session.key?(:ttl) && session.key?(:max_ttl) && (session[:ttl] < Frikandel::Configuration.ttl.ago || session[:max_ttl] < Time.now)
17
- on_expired_session
18
- end
19
- end
20
-
21
- def persist_session_timestamp
22
- session[:ttl] = Time.now
23
- session[:max_ttl] ||= Frikandel::Configuration.max_ttl.from_now
24
- end
25
-
26
- def on_expired_session
27
- reset_session
28
- redirect_to root_path
29
- end
30
- alias original_on_expired_session on_expired_session
31
- end
32
- end
3
+ require 'frikandel/session_invalidation'
4
+ require 'frikandel/limit_session_lifetime'
5
+ require 'frikandel/bind_session_to_ip_address'
@@ -0,0 +1,43 @@
1
+ module Frikandel
2
+ module BindSessionToIpAddress
3
+ extend ActiveSupport::Concern
4
+ include SessionInvalidation
5
+
6
+ included do
7
+ if respond_to?(:before_action)
8
+ append_before_action :validate_session_ip_address
9
+ else
10
+ append_before_filter :validate_session_ip_address
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def validate_session_ip_address
17
+ if session.key?(:ip_address) && !ip_address_match_with_current?
18
+ on_invalid_session
19
+ elsif !session.key?(:ip_address)
20
+ reset_session
21
+ else # session ip address is valid
22
+ persist_session_ip_address
23
+ end
24
+ end
25
+
26
+ def persist_session_ip_address
27
+ session[:ip_address] = current_ip_address
28
+ end
29
+
30
+ def current_ip_address
31
+ request.remote_ip
32
+ end
33
+
34
+ def ip_address_match_with_current?
35
+ session[:ip_address] == current_ip_address
36
+ end
37
+
38
+ def reset_session
39
+ super
40
+ persist_session_ip_address
41
+ end
42
+ end
43
+ end
@@ -1,7 +1,9 @@
1
+ require 'singleton'
2
+
1
3
  module Frikandel
2
4
  class Configuration
3
- include Singleton
4
- extend SingleForwardable
5
+ include ::Singleton
6
+ extend ::SingleForwardable
5
7
 
6
8
  attr_accessor :ttl, :max_ttl
7
9
 
@@ -0,0 +1,44 @@
1
+ module Frikandel
2
+ module LimitSessionLifetime
3
+ extend ActiveSupport::Concern
4
+ include SessionInvalidation
5
+
6
+ included do
7
+ if respond_to?(:before_action)
8
+ append_before_action :validate_session_timestamp
9
+ else
10
+ append_before_filter :validate_session_timestamp
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def validate_session_timestamp
17
+ if session.key?(:ttl) && session.key?(:max_ttl) && (reached_ttl? || reached_max_ttl?)
18
+ on_invalid_session
19
+ elsif !session.key?(:ttl) || !session.key?(:max_ttl)
20
+ reset_session
21
+ else # session timestamp is valid
22
+ persist_session_timestamp
23
+ end
24
+ end
25
+
26
+ def reached_ttl?
27
+ session[:ttl] < Frikandel::Configuration.ttl.ago
28
+ end
29
+
30
+ def reached_max_ttl?
31
+ session[:max_ttl] < Time.now
32
+ end
33
+
34
+ def persist_session_timestamp
35
+ session[:ttl] = Time.now
36
+ session[:max_ttl] ||= Frikandel::Configuration.max_ttl.since
37
+ end
38
+
39
+ def reset_session
40
+ super
41
+ persist_session_timestamp
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ module Frikandel
2
+ module SessionInvalidation
3
+
4
+ private
5
+
6
+ def on_invalid_session
7
+ reset_session
8
+ redirect_to root_path
9
+ end
10
+ alias original_on_invalid_session on_invalid_session
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Frikandel
2
- VERSION = "1.0.0"
2
+ VERSION = "2.2.2"
3
3
  end
@@ -0,0 +1,162 @@
1
+ require "rails_helper"
2
+ require "support/application_controller"
3
+
4
+
5
+ class BindSessionToIpAddressController < ApplicationController
6
+ include Frikandel::BindSessionToIpAddress
7
+
8
+ if respond_to?(:before_action)
9
+ before_action :flash_alert_and_redirect_home, only: [:redirect_home]
10
+ else
11
+ before_filter :flash_alert_and_redirect_home, only: [:redirect_home]
12
+ end
13
+
14
+ def home
15
+ if Rails::VERSION::MAJOR >= 5
16
+ render plain: "bind test"
17
+ else
18
+ render text: "bind test"
19
+ end
20
+ end
21
+
22
+ def redirect_home
23
+ end
24
+
25
+ protected
26
+
27
+ def flash_alert_and_redirect_home
28
+ flash[:alert] = "alert test"
29
+ redirect_to bind_session_to_ip_address_home_url
30
+ end
31
+ end
32
+
33
+
34
+ RSpec.describe BindSessionToIpAddressController do
35
+ context "requests" do
36
+ it "writes current ip address to session" do
37
+ expect(session[:ip_address]).to be_nil
38
+
39
+ get :home
40
+
41
+ expect(session[:ip_address]).to eql("0.0.0.0")
42
+ end
43
+
44
+ it "writes current ip address to session even on redirect in another before filter" do
45
+ expect(session[:ip_address]).to be_nil
46
+
47
+ simulate_redirect!(:redirect_home, :home)
48
+
49
+ expect(session[:ip_address]).to eql("0.0.0.0")
50
+
51
+ expect(flash).not_to be_empty
52
+ expect(flash[:alert]).to eql("alert test")
53
+ end
54
+
55
+ it "raises an exception if session address and current ip address don't match" do
56
+ session[:ip_address] = "1.2.3.4"
57
+ expect(controller).to receive(:on_invalid_session)
58
+
59
+ get :home
60
+ end
61
+
62
+
63
+ context "ip address isn't present in session" do
64
+ it "resets the session and persists the ip address" do
65
+ session[:user_id] = 4337
66
+ session.delete(:ip_address)
67
+ session[:ttl] = "SomeTTL"
68
+ session[:max_ttl] = "SomeMaxTTL"
69
+
70
+ expect(controller).to receive(:reset_session).and_call_original
71
+ expect(controller).to receive(:persist_session_ip_address).and_call_original
72
+ get :home
73
+
74
+ expect(session[:user_id]).to be_blank
75
+ expect(session[:ip_address]).to be_present
76
+ expect(session[:ip_address]).to eql("0.0.0.0")
77
+ expect(session[:ttl]).to be_blank
78
+ expect(session[:max_ttl]).to be_blank
79
+ end
80
+
81
+ it "allows the request to be rendered as normal" do
82
+ get :home
83
+
84
+ expect(response.body).to eql("bind test")
85
+ end
86
+ end
87
+ end
88
+
89
+
90
+ context ".validate_session_ip_address" do
91
+ it "calls on_invalid_session if ip address doesn't match with current" do
92
+ session[:ip_address] = "1.3.3.7"
93
+
94
+ expect(controller).to receive(:ip_address_match_with_current?).and_return(false)
95
+ expect(controller).to receive(:on_invalid_session)
96
+
97
+ controller.send(:validate_session_ip_address)
98
+ end
99
+
100
+ it "calls reset_session if ip address isn't persisted in session" do
101
+ session.delete(:ip_address)
102
+
103
+ expect(controller).not_to receive(:ip_address_match_with_current?)
104
+ expect(controller).to receive(:reset_session)
105
+
106
+ controller.send(:validate_session_ip_address)
107
+ end
108
+
109
+ it "calls persist_session_ip_address if validation passes" do
110
+ session[:ip_address] = "1.3.3.7"
111
+
112
+ expect(controller).to receive(:ip_address_match_with_current?).and_return(true)
113
+ expect(controller).to receive(:persist_session_ip_address)
114
+
115
+ controller.send(:validate_session_ip_address)
116
+ end
117
+ end
118
+
119
+
120
+ context ".persist_session_ip_address" do
121
+ it "sets the current ip address in session on key ip_address" do
122
+ expect {
123
+ expect(controller).to receive(:current_ip_address).and_return("1.3.3.7")
124
+ controller.send(:persist_session_ip_address)
125
+ }.to change {
126
+ session[:ip_address]
127
+ }.from(nil).to("1.3.3.7")
128
+ end
129
+ end
130
+
131
+
132
+ context ".current_ip_address" do
133
+ it "returns the remote_ip from request" do
134
+ expect(request).to receive(:remote_ip).and_return(:request_remote_ip)
135
+
136
+ expect(controller.send(:current_ip_address)).to eql(:request_remote_ip)
137
+ end
138
+ end
139
+
140
+
141
+ context ".ip_address_match_with_current?" do
142
+ it "compares ip address from session with the current ip address" do
143
+ allow(controller).to receive(:current_ip_address).and_return("1.3.3.7")
144
+
145
+ session[:ip_address] = "1.3.3.7"
146
+
147
+ expect(controller.send(:ip_address_match_with_current?)).to be_truthy
148
+
149
+ session[:ip_address] = "7.3.3.1"
150
+
151
+ expect(controller.send(:ip_address_match_with_current?)).to be_falsey
152
+ end
153
+ end
154
+
155
+
156
+ context ".reset_session" do
157
+ it "calls persist_session_ip_address" do
158
+ expect(controller).to receive(:persist_session_ip_address).and_call_original
159
+ controller.send(:reset_session)
160
+ end
161
+ end
162
+ end