panda_pal 5.1.0 → 5.2.4
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 +139 -93
- 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 +13 -0
- data/app/models/panda_pal/organization.rb +6 -3
- data/app/models/panda_pal/{organization → organization_concerns}/settings_validation.rb +21 -5
- data/app/models/panda_pal/{organization → organization_concerns}/task_scheduling.rb +32 -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 +139 -43
- 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 +3 -2
- metadata +32 -8
data/lib/panda_pal.rb
CHANGED
@@ -7,11 +7,11 @@ module PandaPal
|
|
7
7
|
class NotMounted < StandardError;end
|
8
8
|
|
9
9
|
@@lti_navigation = {}
|
10
|
-
@@staged_navigation = {}
|
11
10
|
@@lti_options = {}
|
12
11
|
@@lti_properties = {}
|
13
12
|
@@lti_environments = {}
|
14
13
|
@@lti_custom_params = {}
|
14
|
+
@@lti_private_key = nil
|
15
15
|
|
16
16
|
def self.lti_options= lti_options
|
17
17
|
@@lti_options = lti_options
|
@@ -45,31 +45,44 @@ module PandaPal
|
|
45
45
|
@@lti_custom_params.deep_dup
|
46
46
|
end
|
47
47
|
|
48
|
-
def self.register_navigation(navigation)
|
49
|
-
@@lti_navigation[navigation] ||= {}
|
50
|
-
end
|
51
|
-
|
52
48
|
def self.stage_navigation(navigation, options)
|
53
|
-
@@
|
54
|
-
@@
|
49
|
+
@@lti_navigation[navigation] ||= {}
|
50
|
+
@@lti_navigation[navigation].merge!(options)
|
55
51
|
end
|
56
52
|
|
57
53
|
def self.lti_paths
|
58
54
|
@@lti_navigation.deep_dup
|
59
55
|
end
|
60
56
|
|
61
|
-
def self.
|
62
|
-
@@
|
63
|
-
|
64
|
-
|
65
|
-
|
57
|
+
def self.lti_private_key
|
58
|
+
key = @@lti_private_key.presence
|
59
|
+
key ||= ENV['LTI_PRIVATE_KEY'].presence
|
60
|
+
key ||= File.read(File.join( File.dirname(__FILE__), "../config/dev_lti_key.key")) if Rails.env.development?
|
61
|
+
return nil unless key.present?
|
62
|
+
|
63
|
+
key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
|
64
|
+
key
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.lti_private_key=(v)
|
68
|
+
@@lti_private_key = k
|
66
69
|
end
|
67
70
|
|
68
71
|
private
|
69
72
|
|
70
|
-
def self.
|
71
|
-
|
72
|
-
|
73
|
+
def self.validate_pandapal_config!
|
74
|
+
errors = []
|
75
|
+
validate_lti_navigation(errors)
|
76
|
+
if errors.present?
|
77
|
+
lines = errors.map { |e| " - #{e}" }
|
78
|
+
raise "PandaPal was not configured correctly:\n#{lines.join("\n")}"
|
79
|
+
end
|
73
80
|
end
|
74
81
|
|
82
|
+
def self.validate_lti_navigation(errors = [])
|
83
|
+
@@lti_navigation.each do |k, v|
|
84
|
+
errors << "lti navigation '#{k}' does not have a Route!" unless (LaunchUrlHelpers.launch_url(k) rescue nil)
|
85
|
+
end
|
86
|
+
errors
|
87
|
+
end
|
75
88
|
end
|
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,39 @@
|
|
1
1
|
require 'browser'
|
2
2
|
|
3
3
|
module PandaPal::Helpers::ControllerHelper
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class SessionNonceMismatch < 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
|
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] == payload[:nonce]
|
26
|
+
@current_session = matched_session
|
27
|
+
@current_session.data[:link_nonce] = nil
|
28
|
+
end
|
29
|
+
raise SessionNonceMismatch, "Session Not Found" unless @current_session.present?
|
30
|
+
elsif (session_key = params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]).present?
|
31
|
+
@current_session = PandaPal::Session.find_by(session_key: session_key) if session_key.present?
|
32
|
+
end
|
33
|
+
|
10
34
|
@current_session ||= PandaPal::Session.new(panda_pal_organization_id: current_organization.id)
|
35
|
+
|
36
|
+
@current_session
|
11
37
|
end
|
12
38
|
|
13
39
|
def current_organization
|
@@ -16,17 +42,41 @@ module PandaPal::Helpers::ControllerHelper
|
|
16
42
|
@organization ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
|
17
43
|
end
|
18
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)
|
50
|
+
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
|
+
|
19
56
|
def current_session_data
|
20
57
|
current_session.data
|
21
58
|
end
|
22
59
|
|
60
|
+
def lti_launch_params
|
61
|
+
current_session_data[:launch_params]
|
62
|
+
end
|
63
|
+
|
23
64
|
def session_changed?
|
24
65
|
current_session.changed? && current_session.changes[:data].present?
|
25
66
|
end
|
26
67
|
|
27
68
|
def validate_launch!
|
28
|
-
authorized = false
|
29
69
|
safari_override
|
70
|
+
|
71
|
+
if params[:id_token].present?
|
72
|
+
validate_v1p3_launch
|
73
|
+
elsif params[:oauth_consumer_key].present?
|
74
|
+
validate_v1p0_launch
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_v1p0_launch
|
79
|
+
authorized = false
|
30
80
|
if @organization = params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
|
31
81
|
sanitized_params = request.request_parameters
|
32
82
|
# These params come over with a safari-workaround launch. The authenticator doesn't like them, so clean them out.
|
@@ -37,12 +87,42 @@ module PandaPal::Helpers::ControllerHelper
|
|
37
87
|
authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @organization.secret)
|
38
88
|
authorized = authenticator.valid_signature?
|
39
89
|
end
|
40
|
-
|
90
|
+
|
41
91
|
if !authorized
|
42
92
|
render plain: 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
|
43
|
-
return authorized
|
44
93
|
end
|
45
|
-
|
94
|
+
|
95
|
+
authorized
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate_v1p3_launch
|
99
|
+
decoded_jwt = JSON::JWT.decode(params.require(:id_token), :skip_verification)
|
100
|
+
raise JSON::JWT::VerificationFailed, 'error decoding id_token' if decoded_jwt.blank?
|
101
|
+
|
102
|
+
client_id = decoded_jwt['aud']
|
103
|
+
@organization = PandaPal::Organization.find_by!(key: 'PandaPal') # client_id)
|
104
|
+
raise JSON::JWT::VerificationFailed, 'Unrecognized Organization' unless @organization.present?
|
105
|
+
|
106
|
+
decoded_jwt.verify!(current_lti_platform.public_jwks)
|
107
|
+
|
108
|
+
params[:session_key] = params[:state]
|
109
|
+
raise JSON::JWT::VerificationFailed, 'State is invalid' unless current_session_data[:lti_oauth_nonce] == decoded_jwt['nonce']
|
110
|
+
|
111
|
+
jwt_verifier = PandaPal::LtiJwtValidator.new(decoded_jwt, client_id)
|
112
|
+
raise JSON::JWT::VerificationFailed, jwt_verifier.errors unless jwt_verifier.valid?
|
113
|
+
|
114
|
+
@decoded_lti_jwt = decoded_jwt
|
115
|
+
rescue JSON::JWT::VerificationFailed => e
|
116
|
+
payload = Array(e.message)
|
117
|
+
|
118
|
+
render json: {
|
119
|
+
message: [
|
120
|
+
{ errors: payload },
|
121
|
+
{ id_token: params.require(:id_token) },
|
122
|
+
],
|
123
|
+
}, status: :unauthorized
|
124
|
+
|
125
|
+
false
|
46
126
|
end
|
47
127
|
|
48
128
|
def switch_tenant(organization = current_organization, &block)
|
@@ -59,6 +139,13 @@ module PandaPal::Helpers::ControllerHelper
|
|
59
139
|
safari_override
|
60
140
|
end
|
61
141
|
|
142
|
+
def verify_authenticity_token
|
143
|
+
# No need to check CSRF when no cookies were sent. This fixes CSRF failures in Browsers
|
144
|
+
# that restrict Cookie setting within an IFrame.
|
145
|
+
return unless request.cookies.keys.length > 0
|
146
|
+
super
|
147
|
+
end
|
148
|
+
|
62
149
|
def valid_session?
|
63
150
|
[
|
64
151
|
current_session.persisted?,
|
@@ -66,65 +153,74 @@ module PandaPal::Helpers::ControllerHelper
|
|
66
153
|
current_session.panda_pal_organization_id == current_organization.id,
|
67
154
|
Apartment::Tenant.current == current_organization.name
|
68
155
|
].all?
|
156
|
+
rescue SessionNonceMismatch
|
157
|
+
false
|
69
158
|
end
|
70
159
|
|
71
160
|
def safari_override
|
72
161
|
use_secure_headers_override(:safari_override) if browser.safari?
|
73
162
|
end
|
74
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))
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def link_nonce
|
186
|
+
@link_nonce ||= begin
|
187
|
+
current_session_data[:link_nonce] = SecureRandom.hex
|
188
|
+
|
189
|
+
payload = {
|
190
|
+
session_key: current_session.session_key,
|
191
|
+
organization_id: current_organization.id,
|
192
|
+
nonce: current_session_data[:link_nonce],
|
193
|
+
}
|
194
|
+
|
195
|
+
panda_pal_cryptor.encrypt_and_sign(payload.to_json)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
75
199
|
private
|
200
|
+
|
76
201
|
def organization_key
|
77
|
-
params[:oauth_consumer_key]
|
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
|
78
206
|
end
|
79
207
|
|
80
208
|
def organization_id
|
81
209
|
params[:organization_id]
|
82
210
|
end
|
83
211
|
|
84
|
-
def session_key
|
85
|
-
if params[:encrypted_session_key]
|
86
|
-
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31])
|
87
|
-
return crypt.decrypt_and_verify(params[:encrypted_session_key])
|
88
|
-
end
|
89
|
-
params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]
|
90
|
-
end
|
91
|
-
|
92
212
|
def session_key_header
|
93
213
|
if match = request.headers['Authorization'].try(:match, /token=(.+)/)
|
94
214
|
match[1]
|
95
215
|
end
|
96
216
|
end
|
97
217
|
|
98
|
-
|
99
|
-
|
100
|
-
# url is logged anywhere, it will all be encrypted data. In dev,
|
101
|
-
# just put it in the URL. Putting it in the URL
|
102
|
-
# is insecure, but is fine in development.
|
103
|
-
# Keeping it in the URL in development means that it plays
|
104
|
-
# nicely with webpack-dev-server live reloading (otherwise
|
105
|
-
# you get an access error everytime it tries to live reload).
|
106
|
-
|
107
|
-
def redirect_with_session_to(location, params = {})
|
108
|
-
if Rails.env.development?
|
109
|
-
redirect_development_mode(location, params)
|
110
|
-
else
|
111
|
-
redirect_production_mode(location, params)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def redirect_development_mode(location, params)
|
116
|
-
redirect_to send(location, {
|
117
|
-
session_key: current_session.session_key,
|
118
|
-
organization_id: current_organization.id
|
119
|
-
}.merge(params))
|
218
|
+
def panda_pal_cryptor
|
219
|
+
@panda_pal_cryptor ||= ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31])
|
120
220
|
end
|
121
221
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
redirect_to send(location, {
|
126
|
-
encrypted_session_key: encrypted_data,
|
127
|
-
organization_id: current_organization.id
|
128
|
-
}.merge(params))
|
222
|
+
def auto_save_session
|
223
|
+
yield if block_given?
|
224
|
+
save_session if @current_session && session_changed?
|
129
225
|
end
|
130
226
|
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
|