panda_pal 5.2.5 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +5 -1
- data/lib/panda_pal/helpers/controller_helper.rb +102 -188
- data/lib/panda_pal/helpers/session_replacement.rb +185 -0
- data/lib/panda_pal/version.rb +1 -1
- metadata +5 -15
- data/db/618eef7c0380ba654ad16f867a919e72.sqlite3 +0 -0
- data/db/9ff93d4f7e0e9dc80a43f68997caf4a1.sqlite3 +0 -0
- data/db/a3fda4044a7215bc2c9eb01a4b9e517a.sqlite3 +0 -0
- data/db/daa0e6378a5ec76fcce83b7070dad219.sqlite3 +0 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +0 -15058
- data/spec/dummy/log/test.log +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 42ed3ad8440c5f79c6ccbd20908ffc3e3458b342
|
4
|
+
data.tar.gz: 421fcb272ca0fb0d7c01b191ab4d47da9789d1c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a32296d2975f108022334ec1f32b23a461ee7e37966c639680779198a1941b683ef6bc02fe020a61d8ffa683132f7dc05199d43d8fb2c50ed877082a6129b66
|
7
|
+
data.tar.gz: 8d9f225f68a4b3b4d632f48a3975eab2a61ad61575c9278e06e1f21f41b7df6950750f8b429da47ab45c3e0d8aaf032e32da7b98b01c3a0d1151e049971b5857
|
data/README.md
CHANGED
@@ -369,7 +369,11 @@ You will want to watch out for a few scenarios:
|
|
369
369
|
3) If you use `link_to` and navigate in your LTI (apps that are not single page)
|
370
370
|
make sure you include the `link_nonce` like so:
|
371
371
|
```ruby
|
372
|
-
link_to "Link Name", somewhere_else_path(session_token: link_nonce)
|
372
|
+
link_to "Link Name", somewhere_else_path(arg, session_token: link_nonce)
|
373
|
+
```
|
374
|
+
NB: As of PandaPal 5.2.6, you can instead use
|
375
|
+
```ruby
|
376
|
+
link_to "Name", url_with_session(:somewhere_else_path, arg, kwarg: 1)
|
373
377
|
```
|
374
378
|
|
375
379
|
### Previous Safari Instructions
|
@@ -1,226 +1,140 @@
|
|
1
1
|
require 'browser'
|
2
|
+
require_relative 'session_replacement'
|
2
3
|
|
3
|
-
module PandaPal::Helpers
|
4
|
-
|
4
|
+
module PandaPal::Helpers
|
5
|
+
module ControllerHelper
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include SessionReplacement
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
after_action :auto_save_session
|
12
|
-
end
|
13
|
-
|
14
|
-
def save_session
|
15
|
-
current_session.try(:save)
|
16
|
-
end
|
17
|
-
|
18
|
-
def current_session
|
19
|
-
return @current_session if @current_session.present?
|
20
|
-
|
21
|
-
if params[:session_token]
|
22
|
-
payload = JSON.parse(panda_pal_cryptor.decrypt_and_verify(params[:session_token])).with_indifferent_access
|
23
|
-
matched_session = PandaPal::Session.find_by(session_key: payload[:session_key])
|
9
|
+
def current_organization
|
10
|
+
@organization ||= PandaPal::Organization.find_by!(key: organization_key) if organization_key
|
11
|
+
@organization ||= PandaPal::Organization.find_by(id: organization_id) if organization_id
|
12
|
+
@organization ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
|
13
|
+
end
|
24
14
|
|
25
|
-
|
26
|
-
|
27
|
-
|
15
|
+
def current_lti_platform
|
16
|
+
return @current_lti_platform if @current_lti_platform.present?
|
17
|
+
# TODO: (Future) This could be expanded more to take better advantage of the LTI 1.3 Multi-Tenancy model.
|
18
|
+
if (canvas_url = current_organization&.settings&.dig(:canvas, :base_url)).present?
|
19
|
+
@current_lti_platform ||= PandaPal::Platform::Canvas.new(canvas_url)
|
28
20
|
end
|
29
|
-
|
30
|
-
|
31
|
-
@
|
21
|
+
@current_lti_platform ||= PandaPal::Platform::Canvas.new('http://localhost:3000') if Rails.env.development?
|
22
|
+
@current_lti_platform ||= PandaPal::Platform::CANVAS
|
23
|
+
@current_lti_platform
|
32
24
|
end
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
@current_session
|
37
|
-
end
|
38
|
-
|
39
|
-
def current_organization
|
40
|
-
@organization ||= PandaPal::Organization.find_by!(key: organization_key) if organization_key
|
41
|
-
@organization ||= PandaPal::Organization.find_by(id: organization_id) if organization_id
|
42
|
-
@organization ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
|
43
|
-
end
|
44
|
-
|
45
|
-
def current_lti_platform
|
46
|
-
return @current_lti_platform if @current_lti_platform.present?
|
47
|
-
# TODO: (Future) This could be expanded more to take better advantage of the LTI 1.3 Multi-Tenancy model.
|
48
|
-
if (canvas_url = current_organization&.settings&.dig(:canvas, :base_url)).present?
|
49
|
-
@current_lti_platform ||= PandaPal::Platform::Canvas.new(canvas_url)
|
26
|
+
def lti_launch_params
|
27
|
+
current_session_data[:launch_params]
|
50
28
|
end
|
51
|
-
@current_lti_platform ||= PandaPal::Platform::Canvas.new('http://localhost:3000') if Rails.env.development?
|
52
|
-
@current_lti_platform ||= PandaPal::Platform::CANVAS
|
53
|
-
@current_lti_platform
|
54
|
-
end
|
55
|
-
|
56
|
-
def current_session_data
|
57
|
-
current_session.data
|
58
|
-
end
|
59
|
-
|
60
|
-
def lti_launch_params
|
61
|
-
current_session_data[:launch_params]
|
62
|
-
end
|
63
|
-
|
64
|
-
def session_changed?
|
65
|
-
current_session.changed? && current_session.changes[:data].present?
|
66
|
-
end
|
67
29
|
|
68
|
-
|
69
|
-
|
30
|
+
def validate_launch!
|
31
|
+
safari_override
|
70
32
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def validate_v1p0_launch
|
79
|
-
authorized = false
|
80
|
-
if @organization = params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
|
81
|
-
sanitized_params = request.request_parameters
|
82
|
-
# These params come over with a safari-workaround launch. The authenticator doesn't like them, so clean them out.
|
83
|
-
safe_unexpected_params = ["full_win_launch_requested", "platform_redirect_url", "dummy_param"]
|
84
|
-
safe_unexpected_params.each do |p|
|
85
|
-
sanitized_params.delete(p)
|
33
|
+
if params[:id_token].present?
|
34
|
+
validate_v1p3_launch
|
35
|
+
elsif params[:oauth_consumer_key].present?
|
36
|
+
validate_v1p0_launch
|
86
37
|
end
|
87
|
-
authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @organization.secret)
|
88
|
-
authorized = authenticator.valid_signature?
|
89
38
|
end
|
90
39
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
40
|
+
def validate_v1p0_launch
|
41
|
+
authorized = false
|
42
|
+
if @organization = params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
|
43
|
+
sanitized_params = request.request_parameters
|
44
|
+
# These params come over with a safari-workaround launch. The authenticator doesn't like them, so clean them out.
|
45
|
+
safe_unexpected_params = ["full_win_launch_requested", "platform_redirect_url", "dummy_param"]
|
46
|
+
safe_unexpected_params.each do |p|
|
47
|
+
sanitized_params.delete(p)
|
48
|
+
end
|
49
|
+
authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @organization.secret)
|
50
|
+
authorized = authenticator.valid_signature?
|
51
|
+
end
|
97
52
|
|
98
|
-
|
99
|
-
|
100
|
-
|
53
|
+
if !authorized
|
54
|
+
render plain: 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
|
55
|
+
end
|
101
56
|
|
102
|
-
|
103
|
-
|
104
|
-
raise JSON::JWT::VerificationFailed, 'Unrecognized Organization' unless @organization.present?
|
57
|
+
authorized
|
58
|
+
end
|
105
59
|
|
106
|
-
|
60
|
+
def validate_v1p3_launch
|
61
|
+
decoded_jwt = JSON::JWT.decode(params.require(:id_token), :skip_verification)
|
62
|
+
raise JSON::JWT::VerificationFailed, 'error decoding id_token' if decoded_jwt.blank?
|
107
63
|
|
108
|
-
|
109
|
-
|
64
|
+
client_id = decoded_jwt['aud']
|
65
|
+
@organization = PandaPal::Organization.find_by!(key: 'PandaPal') # client_id)
|
66
|
+
raise JSON::JWT::VerificationFailed, 'Unrecognized Organization' unless @organization.present?
|
110
67
|
|
111
|
-
|
112
|
-
raise JSON::JWT::VerificationFailed, jwt_verifier.errors unless jwt_verifier.valid?
|
68
|
+
decoded_jwt.verify!(current_lti_platform.public_jwks)
|
113
69
|
|
114
|
-
|
115
|
-
|
116
|
-
payload = Array(e.message)
|
70
|
+
params[:session_key] = params[:state]
|
71
|
+
raise JSON::JWT::VerificationFailed, 'State is invalid' unless current_session_data[:lti_oauth_nonce] == decoded_jwt['nonce']
|
117
72
|
|
118
|
-
|
119
|
-
|
120
|
-
{ errors: payload },
|
121
|
-
{ id_token: params.require(:id_token) },
|
122
|
-
],
|
123
|
-
}, status: :unauthorized
|
73
|
+
jwt_verifier = PandaPal::LtiJwtValidator.new(decoded_jwt, client_id)
|
74
|
+
raise JSON::JWT::VerificationFailed, jwt_verifier.errors unless jwt_verifier.valid?
|
124
75
|
|
125
|
-
|
126
|
-
|
76
|
+
@decoded_lti_jwt = decoded_jwt
|
77
|
+
rescue JSON::JWT::VerificationFailed => e
|
78
|
+
payload = Array(e.message)
|
127
79
|
|
128
|
-
|
129
|
-
|
130
|
-
|
80
|
+
render json: {
|
81
|
+
message: [
|
82
|
+
{ errors: payload },
|
83
|
+
{ id_token: params.require(:id_token) },
|
84
|
+
],
|
85
|
+
}, status: :unauthorized
|
131
86
|
|
132
|
-
|
133
|
-
yield
|
87
|
+
false
|
134
88
|
end
|
135
|
-
end
|
136
89
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
end
|
90
|
+
def switch_tenant(organization = current_organization, &block)
|
91
|
+
return unless organization
|
92
|
+
raise 'This method should be called in an around_action callback' unless block_given?
|
141
93
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
return unless request.cookies.keys.length > 0
|
146
|
-
super
|
147
|
-
end
|
148
|
-
|
149
|
-
def valid_session?
|
150
|
-
[
|
151
|
-
current_session.persisted?,
|
152
|
-
current_organization,
|
153
|
-
current_session.panda_pal_organization_id == current_organization.id,
|
154
|
-
Apartment::Tenant.current == current_organization.name
|
155
|
-
].all?
|
156
|
-
rescue SessionNonceMismatch
|
157
|
-
false
|
158
|
-
end
|
159
|
-
|
160
|
-
def safari_override
|
161
|
-
use_secure_headers_override(:safari_override) if browser.safari?
|
162
|
-
end
|
163
|
-
|
164
|
-
# Redirect with the session key intact. In production,
|
165
|
-
# handle this by adding a one-time use encrypted token to the URL.
|
166
|
-
# Keeping it in the URL in development means that it plays
|
167
|
-
# nicely with webpack-dev-server live reloading (otherwise
|
168
|
-
# you get an access error everytime it tries to live reload).
|
169
|
-
|
170
|
-
def redirect_with_session_to(location, params = {}, route_context: self, **rest)
|
171
|
-
params.merge!(rest)
|
172
|
-
if Rails.env.development?
|
173
|
-
redirect_to route_context.send(location, {
|
174
|
-
session_key: current_session.session_key,
|
175
|
-
organization_id: current_organization.id,
|
176
|
-
}.merge(params))
|
177
|
-
else
|
178
|
-
redirect_to route_context.send(location, {
|
179
|
-
session_token: link_nonce,
|
180
|
-
organization_id: current_organization.id,
|
181
|
-
}.merge(params))
|
94
|
+
Apartment::Tenant.switch(organization.name) do
|
95
|
+
yield
|
96
|
+
end
|
182
97
|
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def link_nonce
|
186
|
-
@link_nonce ||= begin
|
187
|
-
current_session_data[:link_nonce] = SecureRandom.hex
|
188
98
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
}
|
99
|
+
def forbid_access_if_lacking_session
|
100
|
+
super
|
101
|
+
safari_override
|
102
|
+
end
|
194
103
|
|
195
|
-
|
104
|
+
def valid_session?
|
105
|
+
[
|
106
|
+
current_session&.persisted?,
|
107
|
+
current_organization,
|
108
|
+
current_session&.panda_pal_organization_id == current_organization.id,
|
109
|
+
Apartment::Tenant.current == current_organization.name
|
110
|
+
].all?
|
111
|
+
rescue SessionNonceMismatch
|
112
|
+
false
|
196
113
|
end
|
197
|
-
end
|
198
114
|
|
199
|
-
|
115
|
+
def safari_override
|
116
|
+
use_secure_headers_override(:safari_override) if browser.safari?
|
117
|
+
end
|
200
118
|
|
201
|
-
|
202
|
-
org_key ||= params[:oauth_consumer_key]
|
203
|
-
org_key ||= "#{params[:client_id]}/#{params[:deployment_id]}" if params[:client_id].present?
|
204
|
-
org_key ||= session[:organization_key]
|
205
|
-
org_key
|
206
|
-
end
|
119
|
+
private
|
207
120
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
match[1]
|
121
|
+
def find_or_create_session(key:)
|
122
|
+
if key == :create
|
123
|
+
PandaPal::Session.new(panda_pal_organization_id: current_organization.id)
|
124
|
+
else
|
125
|
+
PandaPal::Session.find_by(session_key: key)
|
126
|
+
end
|
215
127
|
end
|
216
|
-
end
|
217
128
|
|
218
|
-
|
219
|
-
|
220
|
-
|
129
|
+
def organization_key
|
130
|
+
org_key ||= params[:oauth_consumer_key]
|
131
|
+
org_key ||= "#{params[:client_id]}/#{params[:deployment_id]}" if params[:client_id].present?
|
132
|
+
org_key ||= session[:organization_key]
|
133
|
+
org_key
|
134
|
+
end
|
221
135
|
|
222
|
-
|
223
|
-
|
224
|
-
|
136
|
+
def organization_id
|
137
|
+
params[:organization_id]
|
138
|
+
end
|
225
139
|
end
|
226
140
|
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module PandaPal::Helpers
|
2
|
+
class SessionNonceMismatch < StandardError; end
|
3
|
+
|
4
|
+
module SessionReplacement
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
helper_method :link_nonce, :current_session, :current_session_data
|
9
|
+
helper_method :link_with_session_to, :url_with_session, :session_url_for
|
10
|
+
|
11
|
+
prepend_around_action :monkeypatch_flash
|
12
|
+
prepend_around_action :auto_save_session
|
13
|
+
end
|
14
|
+
|
15
|
+
class_methods do
|
16
|
+
def link_nonce_type(value = :not_given)
|
17
|
+
if value == :not_given
|
18
|
+
@link_nonce_type || superclass.try(:link_nonce_type) || :nonce
|
19
|
+
else
|
20
|
+
@link_nonce_type = value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def save_session
|
26
|
+
current_session.try(:save)
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_session
|
30
|
+
return @current_session if @current_session.present?
|
31
|
+
|
32
|
+
if params[:session_token]
|
33
|
+
payload = JSON.parse(session_cryptor.decrypt_and_verify(params[:session_token])).with_indifferent_access
|
34
|
+
matched_session = find_or_create_session(key: payload[:session_key])
|
35
|
+
|
36
|
+
if matched_session.present?
|
37
|
+
if payload[:token_type] == 'nonce' && matched_session.data[:link_nonce] == payload[:nonce]
|
38
|
+
@current_session = matched_session
|
39
|
+
@current_session.data[:link_nonce] = nil
|
40
|
+
elsif payload[:token_type] == 'fixed_ip' && matched_session.data[:remote_ip] == request.remote_ip &&
|
41
|
+
DateTime.parse(matched_session.data[:last_ip_token_requested]) > 15.minutes.ago
|
42
|
+
@current_session = matched_session
|
43
|
+
end
|
44
|
+
end
|
45
|
+
raise SessionNonceMismatch, "Session Not Found" unless @current_session.present?
|
46
|
+
elsif (session_key = params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]).present?
|
47
|
+
@current_session = find_or_create_session(key: session_key)
|
48
|
+
end
|
49
|
+
|
50
|
+
@current_session ||= find_or_create_session(key: :create)
|
51
|
+
|
52
|
+
@current_session
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_session_data
|
56
|
+
current_session.data
|
57
|
+
end
|
58
|
+
|
59
|
+
def session_changed?
|
60
|
+
current_session.changed? && current_session.changes[:data].present?
|
61
|
+
end
|
62
|
+
|
63
|
+
def forbid_access_if_lacking_session
|
64
|
+
render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
|
65
|
+
end
|
66
|
+
|
67
|
+
def verify_authenticity_token
|
68
|
+
# No need to check CSRF when no cookies were sent. This fixes CSRF failures in Browsers
|
69
|
+
# that restrict Cookie setting within an IFrame.
|
70
|
+
return unless request.cookies.keys.length > 0
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
# Redirect with the session key intact. In production,
|
75
|
+
# handle this by adding a one-time use encrypted token to the URL.
|
76
|
+
# Keeping it in the URL in development means that it plays
|
77
|
+
# nicely with webpack-dev-server live reloading (otherwise
|
78
|
+
# you get an access error everytime it tries to live reload).
|
79
|
+
|
80
|
+
def redirect_with_session_to(*args)
|
81
|
+
redirect_to url_with_session(*args)
|
82
|
+
end
|
83
|
+
|
84
|
+
def link_with_session_to(*args)
|
85
|
+
helpers.link_to url_with_session(*args)
|
86
|
+
end
|
87
|
+
|
88
|
+
def session_url_for(*args)
|
89
|
+
url_for(build_session_url_params(*args))
|
90
|
+
end
|
91
|
+
|
92
|
+
def url_with_session(location, *args, route_context: self, **kwargs)
|
93
|
+
route_context.send(location, *build_session_url_params(*args, **kwargs))
|
94
|
+
end
|
95
|
+
|
96
|
+
def link_nonce(type: link_nonce_type)
|
97
|
+
type = instance_exec(&type) if type.is_a?(Proc)
|
98
|
+
type = type.to_s
|
99
|
+
|
100
|
+
@cached_link_nonces ||= {}
|
101
|
+
@cached_link_nonces[type] ||= begin
|
102
|
+
payload = {
|
103
|
+
token_type: type,
|
104
|
+
session_key: current_session.session_key,
|
105
|
+
organization_id: current_organization.id,
|
106
|
+
}
|
107
|
+
|
108
|
+
if type == 'nonce'
|
109
|
+
current_session_data[:link_nonce] = SecureRandom.hex
|
110
|
+
payload.merge!(nonce: current_session_data[:link_nonce])
|
111
|
+
elsif type == 'fixed_ip'
|
112
|
+
current_session_data[:remote_ip] ||= request.remote_ip
|
113
|
+
current_session_data[:last_ip_token_requested] = DateTime.now.iso8601
|
114
|
+
else
|
115
|
+
raise StandardError, "Unsupported link_nonce_type: '#{type}'"
|
116
|
+
end
|
117
|
+
|
118
|
+
session_cryptor.encrypt_and_sign(payload.to_json)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def link_nonce_type
|
123
|
+
self.class.link_nonce_type
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def session_cryptor
|
129
|
+
secret_key_base = Rails.application.try(:secret_key_base) || Rails.application.secrets.secret_key_base
|
130
|
+
@session_cryptor ||= ActiveSupport::MessageEncryptor.new(secret_key_base[0..31])
|
131
|
+
end
|
132
|
+
|
133
|
+
def session_key_header
|
134
|
+
if match = request.headers['Authorization'].try(:match, /token=(.+)/)
|
135
|
+
match[1]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def build_session_url_params(*args, nonce_type: link_nonce_type, **kwargs)
|
140
|
+
if args[-1].is_a?(Hash)
|
141
|
+
args[-1] = args[-1].dup
|
142
|
+
else
|
143
|
+
args.push({})
|
144
|
+
end
|
145
|
+
|
146
|
+
if Rails.env.development?
|
147
|
+
args[-1].merge!(
|
148
|
+
session_key: current_session.session_key,
|
149
|
+
organization_id: current_organization.id,
|
150
|
+
)
|
151
|
+
else
|
152
|
+
args[-1].merge!(
|
153
|
+
session_token: link_nonce(type: nonce_type),
|
154
|
+
organization_id: current_organization.id,
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
args[-1].merge!(kwargs)
|
159
|
+
args
|
160
|
+
end
|
161
|
+
|
162
|
+
def auto_save_session
|
163
|
+
yield if block_given?
|
164
|
+
save_session if @current_session && session_changed?
|
165
|
+
end
|
166
|
+
|
167
|
+
def monkeypatch_flash
|
168
|
+
if valid_session? && (value = current_session_data['flashes']).present?
|
169
|
+
flashes = value["flashes"]
|
170
|
+
if discard = value["discard"]
|
171
|
+
flashes.except!(*discard)
|
172
|
+
end
|
173
|
+
flash.replace(flashes)
|
174
|
+
flash.discard()
|
175
|
+
end
|
176
|
+
|
177
|
+
yield
|
178
|
+
|
179
|
+
if @current_session.present?
|
180
|
+
current_session_data['flashes'] = flash.to_session_value
|
181
|
+
flash.discard()
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|