vidibus-xss 0.1.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.
- data/.gitignore +21 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +29 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/app/controllers/xss_controller.rb +7 -0
- data/config/routes.rb +3 -0
- data/lib/vidibus/xss/extensions/controller.rb +295 -0
- data/lib/vidibus/xss/extensions/string.rb +22 -0
- data/lib/vidibus/xss/extensions/view.rb +15 -0
- data/lib/vidibus/xss/extensions.rb +9 -0
- data/lib/vidibus/xss/mime_type.rb +1 -0
- data/lib/vidibus/xss.rb +2 -0
- data/lib/vidibus-xss.rb +7 -0
- data/public/javascripts/jquery.ba-bbq.js +1137 -0
- data/public/javascripts/vidibus.js +193 -0
- data/public/javascripts/vidibus.xss.js +335 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +11 -0
- data/vidibus-xss.gemspec +77 -0
- metadata +174 -0
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 André Pankratz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
= vidibus-xss
|
2
|
+
|
3
|
+
This gem is part of the open source SOA framework Vidibus: http://www.vidibus.org
|
4
|
+
|
5
|
+
TODO: describe
|
6
|
+
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
Add the dependency to the Gemfile of your application:
|
11
|
+
|
12
|
+
gem "vidibus-xss"
|
13
|
+
|
14
|
+
Then call bundle install on your console.
|
15
|
+
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
TODO: describe
|
20
|
+
|
21
|
+
|
22
|
+
== Copyright
|
23
|
+
|
24
|
+
Copyright (c) 2010 Andre Pankratz. See LICENSE for details.
|
25
|
+
|
26
|
+
|
27
|
+
== Thank you!
|
28
|
+
|
29
|
+
The development of this gem was sponsored by Käuferportal: http://www.kaeuferportal.de
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "jeweler"
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "vidibus-xss"
|
8
|
+
gem.summary = %Q{Drop-in XSS support for remote applications.}
|
9
|
+
gem.description = %Q{Drop-in XSS support for remote applications.}
|
10
|
+
gem.email = "andre@vidibus.com"
|
11
|
+
gem.homepage = "http://github.com/vidibus/vidibus-xss"
|
12
|
+
gem.authors = ["Andre Pankratz"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "relevance-rcov"
|
15
|
+
gem.add_development_dependency "rr"
|
16
|
+
gem.add_dependency "rails", ">= 3.0.0.rc"
|
17
|
+
gem.add_dependency "nokogiri"
|
18
|
+
gem.add_dependency "vidibus-routing_error"
|
19
|
+
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
require "spec/rake/spectask"
|
28
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
29
|
+
spec.libs << "lib" << "spec"
|
30
|
+
spec.spec_files = FileList["spec/**/*_spec.rb"]
|
31
|
+
end
|
32
|
+
|
33
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
34
|
+
t.spec_files = FileList["spec/vidibus/**/*_spec.rb"]
|
35
|
+
t.rcov = true
|
36
|
+
t.rcov_opts = ["--exclude", "^spec,/gems/"]
|
37
|
+
end
|
38
|
+
|
39
|
+
task :spec => :check_dependencies
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require "rake/rdoctask"
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?("VERSION") ? File.read("VERSION") : ""
|
45
|
+
rdoc.rdoc_dir = "rdoc"
|
46
|
+
rdoc.title = "vidibus-xss #{version}"
|
47
|
+
rdoc.rdoc_files.include("README*")
|
48
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
49
|
+
rdoc.options << "--charset=utf-8"
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.4
|
data/config/routes.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
require "nokogiri"
|
2
|
+
|
3
|
+
module Vidibus
|
4
|
+
module Xss
|
5
|
+
module Extensions
|
6
|
+
module Controller
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
helper_method :url_for, :xss_request?, :fullpath_url
|
11
|
+
#layout :get_layout
|
12
|
+
|
13
|
+
respond_to :html, :xss
|
14
|
+
|
15
|
+
rescue_from ActionController::RoutingError, :with => :rescue_404
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# Returns layout for current request format.
|
21
|
+
def get_layout(format = nil)
|
22
|
+
(xss_request? or format == :xss) ? 'xss.haml' : 'application'
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# IMPORTANT: restart server to apply modifications.
|
27
|
+
def rescue_404
|
28
|
+
return if respond_to_options_request
|
29
|
+
super
|
30
|
+
#render :template => "shared/404", :status => 404
|
31
|
+
end
|
32
|
+
|
33
|
+
# Responds to OPTIONS request.
|
34
|
+
# When sending data to foreign domain by AJAX, Firefox will send an OPTIONS request first.
|
35
|
+
#
|
36
|
+
# == Usage:
|
37
|
+
#
|
38
|
+
# Set up a catch-all route for handling 404s like this, if you haven't done it already:
|
39
|
+
#
|
40
|
+
# match "*path" => "application#rescue_404"
|
41
|
+
#
|
42
|
+
# In ApplicationController, define a method that will be called by this catch-all route:
|
43
|
+
#
|
44
|
+
# def rescue_404
|
45
|
+
# respond_to_options_request
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
def respond_to_options_request
|
49
|
+
return unless options_request?
|
50
|
+
xss_access_control_headers
|
51
|
+
render(:text => "OK", :status => 200) and return true
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns true if current request is in XSS format.
|
55
|
+
def xss_request?
|
56
|
+
@is_xss ||= begin
|
57
|
+
if request.format == :xss
|
58
|
+
true
|
59
|
+
elsif request.format == "*/*"
|
60
|
+
if env["REQUEST_URI"].match(/[^\?]+\.xss/) # try to detect format by file extension
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns true if the current request is an OPTIONS request.
|
68
|
+
def options_request?
|
69
|
+
@is_options_request ||= request.method == "OPTIONS"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set access control headers to allow cross-domain XMLHttpRequest calls.
|
73
|
+
# For more information, see: https://developer.mozilla.org/En/HTTP_access_control
|
74
|
+
def xss_access_control_headers
|
75
|
+
headers["Access-Control-Allow-Origin"] = "*"
|
76
|
+
headers["Access-Control-Allow-Methods"] = "GET, PUT, POST, OPTIONS"
|
77
|
+
end
|
78
|
+
|
79
|
+
def extract_xss_html(dom)
|
80
|
+
dom.css('body').first.inner_html
|
81
|
+
end
|
82
|
+
|
83
|
+
# Extracts javascript resources from given DOM object.
|
84
|
+
#
|
85
|
+
# Usage:
|
86
|
+
#
|
87
|
+
# dom = Nokogiri::HTML(<html></html>)
|
88
|
+
# urls = extract_xss_javascripts(dom)
|
89
|
+
#
|
90
|
+
def extract_xss_javascripts(dom)
|
91
|
+
resources = []
|
92
|
+
for resource in dom.css('head script[type="text/javascript"]')
|
93
|
+
path = resource.attributes["src"].value
|
94
|
+
file = url_for(path, :only_path => false)
|
95
|
+
resources << { :type => "text/javascript", :src => file }
|
96
|
+
end
|
97
|
+
resources
|
98
|
+
end
|
99
|
+
|
100
|
+
# Extracts stylesheet resources from given DOM object.
|
101
|
+
#
|
102
|
+
# Usage:
|
103
|
+
#
|
104
|
+
# dom = Nokogiri::HTML(<html></html>)
|
105
|
+
# urls = extract_xss_stylesheets(dom)
|
106
|
+
#
|
107
|
+
def extract_xss_stylesheets(dom)
|
108
|
+
resources = []
|
109
|
+
for resource in dom.css('head link[type="text/css"]')
|
110
|
+
path = resource.attributes["href"].value
|
111
|
+
file = url_for(path, :only_path => false)
|
112
|
+
media = resource.attributes["media"].value
|
113
|
+
resources << { :type => "text/css", :src => file, :media => media }
|
114
|
+
end
|
115
|
+
resources
|
116
|
+
end
|
117
|
+
|
118
|
+
# Renders given content string to XSS hash of resources and content.
|
119
|
+
# If html content is given, the method tries to extract title,
|
120
|
+
# stylesheets and javascripts from head and content from body.
|
121
|
+
# TODO: Allow script blocks! Add them to body?
|
122
|
+
# TODO: Allow style blocks?
|
123
|
+
# TODO: Check for html content
|
124
|
+
def render_to_xss(content)
|
125
|
+
dom = Nokogiri::HTML(content)
|
126
|
+
{
|
127
|
+
:resources => extract_xss_javascripts(dom) + extract_xss_stylesheets(dom),
|
128
|
+
:content => extract_xss_html(dom)
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
# Redirect to a xss url.
|
133
|
+
# For POST request, invoke callback action.
|
134
|
+
# For GET request, render redirect action directly.
|
135
|
+
def redirect_xss(url)
|
136
|
+
if request.method == "POST" # request.post? does not work??
|
137
|
+
render_xss_callback(:redirect, :to => url)
|
138
|
+
else
|
139
|
+
render_xss(:redirect => url)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Sends data to callback handler.
|
144
|
+
# Inspired by:
|
145
|
+
# http://paydrotalks.com/posts/45-standard-json-response-for-rails-and-jquery
|
146
|
+
def render_xss_callback(type, options = {})
|
147
|
+
unless [ :ok, :redirect, :error ].include?(type)
|
148
|
+
raise "Invalid XSS response type: #{type}"
|
149
|
+
end
|
150
|
+
|
151
|
+
data = {
|
152
|
+
:status => type,
|
153
|
+
:html => nil,
|
154
|
+
:message => nil,
|
155
|
+
:to => nil
|
156
|
+
}.merge(options)
|
157
|
+
|
158
|
+
render_xss(:callback => data)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Main method for rendering XSS.
|
162
|
+
# Renders given XSS resources and content to string and sets it as response_body.
|
163
|
+
def render_xss(options = {})
|
164
|
+
resources = options.delete(:resources)
|
165
|
+
content = options.delete(:content)
|
166
|
+
path = options.delete(:get)
|
167
|
+
redirect = options.delete(:redirect)
|
168
|
+
callback = options.delete(:callback)
|
169
|
+
|
170
|
+
raise "Please provide :content, :get, :redirect or :callback." unless content or path or redirect or callback
|
171
|
+
raise "Please provide either :content to render or :get location to load. Not both." if content and path
|
172
|
+
|
173
|
+
xss = ""
|
174
|
+
|
175
|
+
# determine scope
|
176
|
+
if !(scope = params[:scope]).blank?
|
177
|
+
scope = "$('##{scope}')"
|
178
|
+
else
|
179
|
+
scope = "$s#{xss_random_string}"
|
180
|
+
xss << %(var #{scope}=vidibus.xss.detectScope();)
|
181
|
+
end
|
182
|
+
|
183
|
+
# set host for current scope
|
184
|
+
xss << %(vidibus.xss.setHost('#{request.protocol}#{request.host_with_port}',#{scope});)
|
185
|
+
|
186
|
+
# render load invocations of XSS resources
|
187
|
+
if resources and resources.any?
|
188
|
+
xss << %(vidibus.loader.load(#{resources.to_json},'#{params[:scope]}');)
|
189
|
+
defer = true
|
190
|
+
end
|
191
|
+
|
192
|
+
# render XSS content
|
193
|
+
xss_content = begin
|
194
|
+
if !content.blank?
|
195
|
+
%(vidibus.xss.embed(#{content.escape_xss.to_json},#{scope});)
|
196
|
+
elsif path
|
197
|
+
%(vidibus.xss.get('#{path}',#{scope});)
|
198
|
+
elsif redirect
|
199
|
+
%(vidibus.xss.redirect('#{redirect}',#{scope});)
|
200
|
+
elsif callback
|
201
|
+
%(vidibus.xss.callback(#{callback.to_json},#{scope});)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# wait until resources have been loaded, before rendering XSS content
|
206
|
+
if defer
|
207
|
+
function_name = "rx#{xss_random_string}"
|
208
|
+
xss_content = %(var #{function_name}=function(){if(vidibus.loader.complete){#{xss_content}}else{window.setTimeout('#{function_name}()',100);}};#{function_name}();)
|
209
|
+
end
|
210
|
+
xss << xss_content
|
211
|
+
xss_access_control_headers
|
212
|
+
self.status = 200 # force success status
|
213
|
+
self.response_body = xss
|
214
|
+
end
|
215
|
+
|
216
|
+
# Generates random string for current cycle.
|
217
|
+
def xss_random_string
|
218
|
+
@xss_random_string ||= begin
|
219
|
+
random = ''
|
220
|
+
3.times { random << rand(1000).to_s }
|
221
|
+
random
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
### Override core extensions
|
227
|
+
|
228
|
+
|
229
|
+
# Bypasses authenticity verification for XSS requests.
|
230
|
+
# TODO: Verify authenticity in other ways (Single Sign-on).
|
231
|
+
def verify_authenticity_token
|
232
|
+
xss_request? || super
|
233
|
+
end
|
234
|
+
|
235
|
+
# Extension of url_for:
|
236
|
+
# Transform given relative paths into absolute urls.
|
237
|
+
#
|
238
|
+
# Usage:
|
239
|
+
# url_for("/stylesheets/vidibus.css", :only_path => false)
|
240
|
+
#
|
241
|
+
def url_for(*args)
|
242
|
+
options, special = args
|
243
|
+
if options.is_a?(String) and special and special[:only_path] == false
|
244
|
+
unless options =~ /\Ahttp/
|
245
|
+
options = "#{request.protocol}#{request.host_with_port}#{options}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
super(options)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Chatches redirect calls for XSS locations.
|
252
|
+
# If a XSS location is given, XSS content will be rendered instead of redirecting.
|
253
|
+
#
|
254
|
+
# == Usage:
|
255
|
+
#
|
256
|
+
# respond_to do |format|
|
257
|
+
# format.xss { redirect_to forms_path }
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
def redirect_to(*args)
|
261
|
+
if xss_request?
|
262
|
+
redirect_xss(args.first)
|
263
|
+
else
|
264
|
+
super
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Extensions of render method:
|
269
|
+
# Renders XSS response if requested
|
270
|
+
def render(*args, &block)
|
271
|
+
args << options = args.extract_options!
|
272
|
+
if xss_request? or options[:format] == :xss
|
273
|
+
|
274
|
+
# embed xss.get
|
275
|
+
if path = options[:path]
|
276
|
+
content = render_to_string(:template => "layouts/#{get_layout(:xss)}")
|
277
|
+
xss = render_to_xss(content)
|
278
|
+
xss[:get] = "/#{path}"
|
279
|
+
xss.delete(:content) # Ensure that not content will be embedded!
|
280
|
+
|
281
|
+
# embed xss content
|
282
|
+
else
|
283
|
+
content = render_to_string(*args, &block)
|
284
|
+
xss = render_to_xss(content)
|
285
|
+
end
|
286
|
+
|
287
|
+
render_xss(xss)
|
288
|
+
else
|
289
|
+
super(*args, &block)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Xss
|
3
|
+
module Extensions
|
4
|
+
module String
|
5
|
+
|
6
|
+
# Prepares XSS content for rendering.
|
7
|
+
def escape_xss
|
8
|
+
regexp = {
|
9
|
+
/^\/\/.+$/ => '', # remove comments
|
10
|
+
# /\n\s*/ => '', # trim indentation and remove linebreaks
|
11
|
+
/\/\/\<!\[CDATA\[(.*?)\/\/\]\]\>/ => "\\1" # remove //<![CDATA[...content...//]]>
|
12
|
+
}
|
13
|
+
c = clone
|
14
|
+
for s, r in regexp
|
15
|
+
c.gsub!(s,r)
|
16
|
+
end
|
17
|
+
c
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|