panda_pal 5.0.0.beta.2 → 5.2.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 +4 -4
- data/README.md +204 -107
- data/app/controllers/panda_pal/lti_controller.rb +0 -18
- data/app/controllers/panda_pal/lti_v1_p0_controller.rb +34 -0
- data/app/controllers/panda_pal/lti_v1_p3_controller.rb +98 -0
- data/app/lib/lti_xml/base_platform.rb +4 -4
- data/app/lib/panda_pal/launch_url_helpers.rb +69 -0
- data/app/lib/panda_pal/lti_jwt_validator.rb +88 -0
- data/app/lib/panda_pal/misc_helper.rb +11 -0
- data/app/models/panda_pal/organization.rb +21 -47
- data/app/models/panda_pal/organization_concerns/settings_validation.rb +127 -0
- data/app/models/panda_pal/organization_concerns/task_scheduling.rb +204 -0
- data/app/models/panda_pal/platform.rb +40 -0
- data/app/views/panda_pal/lti_v1_p3/login.html.erb +1 -0
- data/app/views/panda_pal/partials/_auto_submit_form.html.erb +9 -0
- data/config/dev_lti_key.key +27 -0
- data/config/routes.rb +12 -2
- data/db/migrate/20160412205931_create_panda_pal_organizations.rb +1 -1
- data/db/migrate/20160413135653_create_panda_pal_sessions.rb +1 -1
- data/db/migrate/20160425130344_add_panda_pal_organization_to_session.rb +1 -1
- data/db/migrate/20170106165533_add_salesforce_id_to_organizations.rb +1 -1
- data/db/migrate/20171205183457_encrypt_organization_settings.rb +1 -1
- data/db/migrate/20171205194657_remove_old_organization_settings.rb +8 -3
- data/lib/panda_pal.rb +28 -15
- data/lib/panda_pal/engine.rb +8 -39
- data/lib/panda_pal/helpers.rb +1 -0
- data/lib/panda_pal/helpers/controller_helper.rb +137 -90
- data/lib/panda_pal/helpers/route_helper.rb +8 -8
- data/lib/panda_pal/helpers/secure_headers.rb +79 -0
- data/lib/panda_pal/version.rb +1 -1
- data/panda_pal.gemspec +6 -2
- data/spec/dummy/config/application.rb +7 -1
- data/spec/dummy/config/environments/development.rb +0 -14
- data/spec/dummy/config/environments/production.rb +0 -11
- data/spec/models/panda_pal/organization/settings_validation_spec.rb +175 -0
- data/spec/models/panda_pal/organization/task_scheduling_spec.rb +144 -0
- data/spec/models/panda_pal/organization_spec.rb +0 -89
- data/spec/spec_helper.rb +4 -0
- metadata +66 -12
- data/app/views/panda_pal/lti/iframe_cookie_authorize.html.erb +0 -19
- data/app/views/panda_pal/lti/iframe_cookie_fix.html.erb +0 -12
- data/spec/dummy/config/initializers/assets.rb +0 -11
data/lib/panda_pal/engine.rb
CHANGED
@@ -38,52 +38,21 @@ module PandaPal
|
|
38
38
|
initializer 'panda_pal.route_options' do |app|
|
39
39
|
ActiveSupport.on_load(:action_controller) do
|
40
40
|
Rails.application.reload_routes!
|
41
|
-
PandaPal::
|
41
|
+
PandaPal::validate_pandapal_config!
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
initializer :secure_headers do |app|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
if Rails.env.development?
|
50
|
-
# Allow webpack-dev-server to work
|
51
|
-
connect_src << "http://localhost:3035"
|
52
|
-
connect_src << "ws://localhost:3035"
|
53
|
-
|
54
|
-
# Allow stuff like rack-mini-profiler to work in development:
|
55
|
-
# https://github.com/MiniProfiler/rack-mini-profiler/issues/327
|
56
|
-
# DON'T ENABLE THIS FOR PRODUCTION!
|
57
|
-
script_src << "'unsafe-eval'"
|
58
|
-
end
|
59
|
-
|
60
|
-
SecureHeaders::Configuration.default do |config|
|
61
|
-
# The default cookie headers aren't compatable with PandaPal cookies currenntly
|
62
|
-
config.cookies = { samesite: { none: true } }
|
63
|
-
|
64
|
-
if Rails.env.production?
|
65
|
-
config.cookies[:secure] = true
|
46
|
+
begin
|
47
|
+
::SecureHeaders::Configuration.default do |config|
|
48
|
+
PandaPal::SecureHeaders.apply_defaults(config)
|
66
49
|
end
|
67
|
-
|
68
|
-
#
|
69
|
-
config.x_frame_options = "ALLOWALL"
|
70
|
-
|
71
|
-
config.x_content_type_options = "nosniff"
|
72
|
-
config.x_xss_protection = "1; mode=block"
|
73
|
-
config.referrer_policy = %w(origin-when-cross-origin strict-origin-when-cross-origin)
|
74
|
-
|
75
|
-
config.csp = {
|
76
|
-
default_src: %w('self'),
|
77
|
-
script_src: script_src,
|
78
|
-
# Certain CSS-in-JS libraries inline the CSS, so we need to use unsafe-inline for them
|
79
|
-
style_src: %w('self' 'unsafe-inline' blob: https://fonts.googleapis.com),
|
80
|
-
font_src: %w('self' data: https://fonts.gstatic.com),
|
81
|
-
connect_src: connect_src,
|
82
|
-
}
|
50
|
+
rescue ::SecureHeaders::Configuration::AlreadyConfiguredError
|
51
|
+
# The App already applied settings
|
83
52
|
end
|
84
53
|
|
85
|
-
SecureHeaders::Configuration.override(:safari_override) do |config|
|
86
|
-
config.cookies = SecureHeaders::OPT_OUT
|
54
|
+
::SecureHeaders::Configuration.override(:safari_override) do |config|
|
55
|
+
config.cookies = ::SecureHeaders::OPT_OUT
|
87
56
|
end
|
88
57
|
end
|
89
58
|
end
|
data/lib/panda_pal/helpers.rb
CHANGED
@@ -1,13 +1,40 @@
|
|
1
1
|
require 'browser'
|
2
2
|
|
3
3
|
module PandaPal::Helpers::ControllerHelper
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class SessionNotFound < StandardError; end
|
7
|
+
|
8
|
+
included do
|
9
|
+
helper_method :link_nonce, :current_session
|
10
|
+
|
11
|
+
after_action :auto_save_session
|
12
|
+
end
|
13
|
+
|
4
14
|
def save_session
|
5
15
|
current_session.try(:save)
|
6
16
|
end
|
7
17
|
|
8
18
|
def current_session
|
9
|
-
@current_session
|
10
|
-
|
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])
|
24
|
+
|
25
|
+
if matched_session.present? && matched_session.data[:link_nonce] == params[:session_token]
|
26
|
+
@current_session = matched_session
|
27
|
+
@current_session.data[:link_nonce] = nil
|
28
|
+
end
|
29
|
+
elsif (session_key = params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]).present?
|
30
|
+
@current_session = PandaPal::Session.find_by(session_key: session_key) if session_key.present?
|
31
|
+
else
|
32
|
+
@current_session = PandaPal::Session.new(panda_pal_organization_id: current_organization.id)
|
33
|
+
end
|
34
|
+
|
35
|
+
raise SessionNotFound, "Session Not Found" unless @current_session.present?
|
36
|
+
|
37
|
+
@current_session
|
11
38
|
end
|
12
39
|
|
13
40
|
def current_organization
|
@@ -16,17 +43,41 @@ module PandaPal::Helpers::ControllerHelper
|
|
16
43
|
@organization ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
|
17
44
|
end
|
18
45
|
|
46
|
+
def current_lti_platform
|
47
|
+
return @current_lti_platform if @current_lti_platform.present?
|
48
|
+
# TODO: (Future) This could be expanded more to take better advantage of the LTI 1.3 Multi-Tenancy model.
|
49
|
+
if (canvas_url = current_organization&.settings&.dig(:canvas, :base_url)).present?
|
50
|
+
@current_lti_platform ||= PandaPal::Platform::Canvas.new(canvas_url)
|
51
|
+
end
|
52
|
+
@current_lti_platform ||= PandaPal::Platform::Canvas.new('http://localhost:3000') if Rails.env.development?
|
53
|
+
@current_lti_platform ||= PandaPal::Platform::CANVAS
|
54
|
+
@current_lti_platform
|
55
|
+
end
|
56
|
+
|
19
57
|
def current_session_data
|
20
58
|
current_session.data
|
21
59
|
end
|
22
60
|
|
61
|
+
def lti_launch_params
|
62
|
+
current_session_data[:launch_params]
|
63
|
+
end
|
64
|
+
|
23
65
|
def session_changed?
|
24
66
|
current_session.changed? && current_session.changes[:data].present?
|
25
67
|
end
|
26
68
|
|
27
69
|
def validate_launch!
|
28
|
-
authorized = false
|
29
70
|
safari_override
|
71
|
+
|
72
|
+
if params[:id_token].present?
|
73
|
+
validate_v1p3_launch
|
74
|
+
elsif params[:oauth_consumer_key].present?
|
75
|
+
validate_v1p0_launch
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate_v1p0_launch
|
80
|
+
authorized = false
|
30
81
|
if @organization = params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
|
31
82
|
sanitized_params = request.request_parameters
|
32
83
|
# These params come over with a safari-workaround launch. The authenticator doesn't like them, so clean them out.
|
@@ -37,32 +88,42 @@ module PandaPal::Helpers::ControllerHelper
|
|
37
88
|
authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @organization.secret)
|
38
89
|
authorized = authenticator.valid_signature?
|
39
90
|
end
|
40
|
-
|
91
|
+
|
41
92
|
if !authorized
|
42
93
|
render plain: 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
|
43
|
-
return authorized
|
44
|
-
end
|
45
|
-
if require_persistent_session
|
46
|
-
if cookies_need_iframe_fix?(false)
|
47
|
-
fix_iframe_cookies
|
48
|
-
return false
|
49
|
-
end
|
50
|
-
# For safari we may have been launched temporarily full-screen by canvas. This allows us to set the session cookie.
|
51
|
-
# In this case, we should make sure the session cookie is fixed and redirect back to canvas to properly launch the embedded LTI.
|
52
|
-
if params[:platform_redirect_url]
|
53
|
-
session[:safari_cookie_fixed] = true
|
54
|
-
redirect_to params[:platform_redirect_url]
|
55
|
-
return false
|
56
|
-
end
|
57
94
|
end
|
58
|
-
|
95
|
+
|
96
|
+
authorized
|
59
97
|
end
|
60
98
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
99
|
+
def validate_v1p3_launch
|
100
|
+
decoded_jwt = JSON::JWT.decode(params.require(:id_token), :skip_verification)
|
101
|
+
raise JSON::JWT::VerificationFailed, 'error decoding id_token' if decoded_jwt.blank?
|
102
|
+
|
103
|
+
client_id = decoded_jwt['aud']
|
104
|
+
@organization = PandaPal::Organization.find_by!(key: 'PandaPal') # client_id)
|
105
|
+
raise JSON::JWT::VerificationFailed, 'Unrecognized Organization' unless @organization.present?
|
106
|
+
|
107
|
+
decoded_jwt.verify!(current_lti_platform.public_jwks)
|
108
|
+
|
109
|
+
params[:session_key] = params[:state]
|
110
|
+
raise JSON::JWT::VerificationFailed, 'State is invalid' unless current_session_data[:lti_oauth_nonce] == decoded_jwt['nonce']
|
111
|
+
|
112
|
+
jwt_verifier = PandaPal::LtiJwtValidator.new(decoded_jwt, client_id)
|
113
|
+
raise JSON::JWT::VerificationFailed, jwt_verifier.errors unless jwt_verifier.valid?
|
114
|
+
|
115
|
+
@decoded_lti_jwt = decoded_jwt
|
116
|
+
rescue JSON::JWT::VerificationFailed => e
|
117
|
+
payload = Array(e.message)
|
118
|
+
|
119
|
+
render json: {
|
120
|
+
message: [
|
121
|
+
{ errors: payload },
|
122
|
+
{ id_token: params.require(:id_token) },
|
123
|
+
],
|
124
|
+
}, status: :unauthorized
|
125
|
+
|
126
|
+
false
|
66
127
|
end
|
67
128
|
|
68
129
|
def switch_tenant(organization = current_organization, &block)
|
@@ -74,38 +135,18 @@ module PandaPal::Helpers::ControllerHelper
|
|
74
135
|
end
|
75
136
|
end
|
76
137
|
|
77
|
-
# Browsers that prevent 3rd party cookies by default (Safari and IE) run into problems
|
78
|
-
# with CSRF handling because the Rails session cookie isn't set. To fix this, we
|
79
|
-
# redirect the current page to the LTI using JavaScript, which will set the cookie,
|
80
|
-
# and then immediately redirect back to Canvas.
|
81
|
-
def fix_iframe_cookies
|
82
|
-
if params[:safari_cookie_authorized].present?
|
83
|
-
session[:safari_cookie_authorized] = true
|
84
|
-
redirect_to params[:return_to]
|
85
|
-
elsif (session[:safari_cookie_fixed] && !params[:safari_cookie_authorized])
|
86
|
-
render 'panda_pal/lti/iframe_cookie_authorize', layout: false
|
87
|
-
else
|
88
|
-
render 'panda_pal/lti/iframe_cookie_fix', layout: false
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def cookies_need_iframe_fix?(check_authorized=true)
|
93
|
-
if check_authorized
|
94
|
-
return browser.safari? && !request.referrer&.include?('sessionless_launch') && !(session[:safari_cookie_fixed] && session[:safari_cookie_authorized]) && !params[:platform_redirect_url]
|
95
|
-
else
|
96
|
-
return browser.safari? && !request.referrer&.include?('sessionless_launch') && !session[:safari_cookie_fixed] && !params[:platform_redirect_url]
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
138
|
def forbid_access_if_lacking_session
|
101
|
-
|
102
|
-
fix_iframe_cookies
|
103
|
-
else
|
104
|
-
render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
|
105
|
-
end
|
139
|
+
render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
|
106
140
|
safari_override
|
107
141
|
end
|
108
142
|
|
143
|
+
def verify_authenticity_token
|
144
|
+
# No need to check CSRF when no cookies were sent. This fixes CSRF failures in Browsers
|
145
|
+
# that restrict Cookie setting within an IFrame.
|
146
|
+
return unless request.cookies.keys.length > 0
|
147
|
+
super
|
148
|
+
end
|
149
|
+
|
109
150
|
def valid_session?
|
110
151
|
[
|
111
152
|
current_session.persisted?,
|
@@ -119,59 +160,65 @@ module PandaPal::Helpers::ControllerHelper
|
|
119
160
|
use_secure_headers_override(:safari_override) if browser.safari?
|
120
161
|
end
|
121
162
|
|
163
|
+
# Redirect with the session key intact. In production,
|
164
|
+
# handle this by adding a one-time use encrypted token to the URL.
|
165
|
+
# Keeping it in the URL in development means that it plays
|
166
|
+
# nicely with webpack-dev-server live reloading (otherwise
|
167
|
+
# you get an access error everytime it tries to live reload).
|
168
|
+
|
169
|
+
def redirect_with_session_to(location, params = {}, route_context: self)
|
170
|
+
if Rails.env.development?
|
171
|
+
redirect_to route_context.send(location, {
|
172
|
+
session_key: current_session.session_key,
|
173
|
+
organization_id: current_organization.id,
|
174
|
+
}.merge(params))
|
175
|
+
else
|
176
|
+
redirect_to route_context.send(location, {
|
177
|
+
session_token: link_nonce,
|
178
|
+
organization_id: current_organization.id,
|
179
|
+
}.merge(params))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def link_nonce
|
184
|
+
@link_nonce ||= begin
|
185
|
+
current_session_data[:link_nonce] = SecureRandom.hex
|
186
|
+
|
187
|
+
payload = {
|
188
|
+
session_key: current_session.session_key,
|
189
|
+
organization_id: current_organization.id,
|
190
|
+
nonce: current_session_data[:link_nonce],
|
191
|
+
}
|
192
|
+
|
193
|
+
panda_pal_cryptor.encrypt_and_sign(payload.to_json)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
122
197
|
private
|
198
|
+
|
123
199
|
def organization_key
|
124
|
-
params[:oauth_consumer_key]
|
200
|
+
org_key ||= params[:oauth_consumer_key]
|
201
|
+
org_key ||= "#{params[:client_id]}/#{params[:deployment_id]}" if params[:client_id].present?
|
202
|
+
org_key ||= session[:organization_key]
|
203
|
+
org_key
|
125
204
|
end
|
126
205
|
|
127
206
|
def organization_id
|
128
207
|
params[:organization_id]
|
129
208
|
end
|
130
209
|
|
131
|
-
def session_key
|
132
|
-
if params[:encrypted_session_key]
|
133
|
-
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
|
134
|
-
return crypt.decrypt_and_verify(params[:encrypted_session_key])
|
135
|
-
end
|
136
|
-
params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]
|
137
|
-
end
|
138
|
-
|
139
210
|
def session_key_header
|
140
211
|
if match = request.headers['Authorization'].try(:match, /token=(.+)/)
|
141
212
|
match[1]
|
142
213
|
end
|
143
214
|
end
|
144
215
|
|
145
|
-
|
146
|
-
|
147
|
-
# url is logged anywhere, it will all be encrypted data. In dev,
|
148
|
-
# just put it in the URL. Putting it in the URL
|
149
|
-
# is insecure, but is fine in development.
|
150
|
-
# Keeping it in the URL in development means that it plays
|
151
|
-
# nicely with webpack-dev-server live reloading (otherwise
|
152
|
-
# you get an access error everytime it tries to live reload).
|
153
|
-
|
154
|
-
def redirect_with_session_to(location, params = {})
|
155
|
-
if Rails.env.development?
|
156
|
-
redirect_development_mode(location, params)
|
157
|
-
else
|
158
|
-
redirect_production_mode(location, params)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def redirect_development_mode(location, params)
|
163
|
-
redirect_to send(location, {
|
164
|
-
session_key: current_session.session_key,
|
165
|
-
organization_id: current_organization.id
|
166
|
-
}.merge(params))
|
216
|
+
def panda_pal_cryptor
|
217
|
+
@panda_pal_cryptor ||= ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31])
|
167
218
|
end
|
168
219
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
redirect_to send(location, {
|
173
|
-
encrypted_session_key: encrypted_data,
|
174
|
-
organization_id: current_organization.id
|
175
|
-
}.merge(params))
|
220
|
+
def auto_save_session
|
221
|
+
yield if block_given?
|
222
|
+
save_session if @current_session && session_changed?
|
176
223
|
end
|
177
224
|
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
module PandaPal::Helpers::RouteHelper
|
2
|
-
def lti_nav(
|
2
|
+
def lti_nav(options, *rest, &block)
|
3
3
|
base_path = Rails.application.routes.named_routes[:panda_pal].path.spec
|
4
4
|
raise LtiNavigationInUse.new('PandaPal must be mounted before defining lti_nav routes') if base_path.blank?
|
5
|
-
|
5
|
+
|
6
6
|
nav, to = options.first
|
7
7
|
options[:to] = to
|
8
8
|
options.delete nav
|
9
|
-
lti_options = options.delete(:lti_options) || {}
|
10
9
|
path = "#{base_path}/#{nav.to_s}"
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
|
11
|
+
lti_options = options.delete(:lti_options) || {}
|
12
|
+
lti_options[:route_helper_key] = path.split('/').reject(&:empty?).join('_')
|
13
|
+
post(path, options.dup, &block)
|
14
|
+
get(path, options.dup, &block)
|
15
|
+
PandaPal::stage_navigation(nav, lti_options)
|
16
16
|
end
|
17
17
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module PandaPal
|
2
|
+
module SecureHeaders
|
3
|
+
def self.apply_defaults(config)
|
4
|
+
@config = config
|
5
|
+
# The default cookie headers aren't compatable with PandaPal cookies currenntly
|
6
|
+
config.cookies = { samesite: { none: true } }
|
7
|
+
|
8
|
+
if Rails.env.production?
|
9
|
+
config.cookies[:secure] = true
|
10
|
+
end
|
11
|
+
|
12
|
+
# Need to allow LTI iframes
|
13
|
+
config.x_frame_options = "ALLOWALL"
|
14
|
+
|
15
|
+
config.x_content_type_options = "nosniff"
|
16
|
+
config.x_xss_protection = "1; mode=block"
|
17
|
+
config.referrer_policy = %w(origin-when-cross-origin strict-origin-when-cross-origin)
|
18
|
+
|
19
|
+
config.csp ||= {}
|
20
|
+
|
21
|
+
csp_entry(:default_src, %w['self'])
|
22
|
+
csp_entry(:connect_src, %w['self'])
|
23
|
+
csp_entry(:script_src, %w['self'])
|
24
|
+
|
25
|
+
if Rails.env.development?
|
26
|
+
# Allow webpack-dev-server to work
|
27
|
+
csp_entry(:connect_src, "http://localhost:3035")
|
28
|
+
csp_entry(:connect_src, "ws://localhost:3035")
|
29
|
+
|
30
|
+
# Allow stuff like rack-mini-profiler to work in development:
|
31
|
+
# https://github.com/MiniProfiler/rack-mini-profiler/issues/327
|
32
|
+
# DON'T ENABLE THIS FOR PRODUCTION!
|
33
|
+
csp_entry(:script_src, "'unsafe-eval'")
|
34
|
+
|
35
|
+
# Detect and permit Scout APM in Dev
|
36
|
+
if MiscHelper.to_boolean(ENV['SCOUT_DEV_TRACE'])
|
37
|
+
csp_entry(:default_src, 'https://scoutapm.com')
|
38
|
+
csp_entry(:default_src, 'https://apm.scoutapp.com')
|
39
|
+
|
40
|
+
csp_entry(:script_src, "'unsafe-inline'")
|
41
|
+
csp_entry(:script_src, 'https://scoutapm.com')
|
42
|
+
csp_entry(:script_src, 'https://apm.scoutapp.com')
|
43
|
+
|
44
|
+
csp_entry(:connect_src, 'https://apm.scoutapp.com')
|
45
|
+
|
46
|
+
csp_entry(:style_src, 'https://scoutapm.com')
|
47
|
+
csp_entry(:style_src, 'https://apm.scoutapp.com')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Detect and permit Sentry
|
52
|
+
if defined?(Raven) && Raven.configuration.server.present?
|
53
|
+
csp_entry(:connect_src, Raven.configuration.server)
|
54
|
+
|
55
|
+
# Report CSP Violations to Sentry
|
56
|
+
unless config.csp[:report_uri].present?
|
57
|
+
cfg = Raven.configuration
|
58
|
+
config.csp[:report_uri] = ["#{cfg.scheme}://#{cfg.host}/api/#{cfg.project_id}/security/?sentry_key=#{cfg.public_key}"] unless config.csp[:report_uri].present?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Certain CSS-in-JS libraries inline the CSS, so we need to use unsafe-inline for them
|
63
|
+
csp_entry(:style_src, %w('self' 'unsafe-inline' blob: https://fonts.googleapis.com))
|
64
|
+
csp_entry(:font_src, %w('self' data: https://fonts.gstatic.com))
|
65
|
+
|
66
|
+
@config = nil
|
67
|
+
|
68
|
+
config
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def self.csp_entry(key, *values)
|
74
|
+
values = values.flatten
|
75
|
+
@config.csp[key] ||= []
|
76
|
+
@config.csp[key] |= values
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|