Class: Cuba

Inherits:
Object
  • Object
show all
Defined in:
lib/cuba/safe/secure_headers.rb,
lib/cuba.rb,
lib/cuba/safe.rb,
lib/cuba/render.rb,
lib/cuba/safe/csrf.rb

Overview

Secure HTTP Headers

This plugin will automatically apply several headers that are related to security. This includes:

- HTTP Strict Transport Security (HSTS) [2].
- X-Frame-Options [3].
- X-XSS-Protection [4].
- X-Content-Type-Options [5].
- X-Download-Options [6].
- X-Permitted-Cross-Domain-Policies [7].

References

[1]: github.com/twitter/secureheaders [2]: tools.ietf.org/html/rfc6797 [3]: tools.ietf.org/html/draft-ietf-websec-x-frame-options-02 [4]: msdn.microsoft.com/en-us/library/dd565647(v=vs.85).aspx [5]: msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx [6]: msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx [7]: www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html

Defined Under Namespace

Modules: Render, Safe Classes: Response

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(&blk) ⇒ Cuba

Returns a new instance of Cuba.



101
102
103
104
# File 'lib/cuba.rb', line 101

def initialize(&blk)
  @blk = blk
  @captures = []
end

Instance Attribute Details

#capturesObject (readonly)

Returns the value of attribute captures.



99
100
101
# File 'lib/cuba.rb', line 99

def captures
  @captures
end

#envObject (readonly)

Returns the value of attribute env.



96
97
98
# File 'lib/cuba.rb', line 96

def env
  @env
end

#reqObject (readonly)

Returns the value of attribute req.



97
98
99
# File 'lib/cuba.rb', line 97

def req
  @req
end

#resObject (readonly)

Returns the value of attribute res.



98
99
100
# File 'lib/cuba.rb', line 98

def res
  @res
end

Class Method Details

.appObject



57
58
59
# File 'lib/cuba.rb', line 57

def self.app
  @app ||= Rack::Builder.new
end

.call(env) ⇒ Object



73
74
75
# File 'lib/cuba.rb', line 73

def self.call(env)
  prototype.call(env)
end

.deepclone(obj) ⇒ Object



88
89
90
# File 'lib/cuba.rb', line 88

def self.deepclone(obj)
  Marshal.load(Marshal.dump(obj))
end

.define(&block) ⇒ Object



65
66
67
# File 'lib/cuba.rb', line 65

def self.define(&block)
  app.run new(&block)
end

.inherited(child) ⇒ Object



92
93
94
# File 'lib/cuba.rb', line 92

def self.inherited(child)
  child.settings.replace(deepclone(settings))
end

.plugin(mixin) ⇒ Object



77
78
79
80
81
82
# File 'lib/cuba.rb', line 77

def self.plugin(mixin)
  include mixin
  extend  mixin::ClassMethods if defined?(mixin::ClassMethods)

  mixin.setup(self) if mixin.respond_to?(:setup)
end

.prototypeObject



69
70
71
# File 'lib/cuba.rb', line 69

def self.prototype
  @prototype ||= app.to_app
end

.reset!Object



52
53
54
55
# File 'lib/cuba.rb', line 52

def self.reset!
  @app = nil
  @prototype = nil
end

.settingsObject



84
85
86
# File 'lib/cuba.rb', line 84

def self.settings
  @settings ||= {}
end

.use(middleware, *args, &block) ⇒ Object



61
62
63
# File 'lib/cuba.rb', line 61

def self.use(middleware, *args, &block)
  app.use(middleware, *args, &block)
end

Instance Method Details

#accept(mimetype) ⇒ Object

If you want to match against the HTTP_ACCEPT value.

Examples:

# HTTP_ACCEPT=application/xml
on accept("application/xml") do
  # automatically set to application/xml.
  res.write res["Content-Type"]
end


277
278
279
280
281
282
283
284
285
# File 'lib/cuba.rb', line 277

def accept(mimetype)
  lambda do
    accept = String(env["HTTP_ACCEPT"]).split(",")

    if accept.any? { |s| s.strip == mimetype }
      res["Content-Type"] = mimetype
    end
  end
end

#call(env) ⇒ Object



110
111
112
# File 'lib/cuba.rb', line 110

def call(env)
  dup.call!(env)
end

#call!(env) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/cuba.rb', line 114

def call!(env)
  @env = env
  @req = settings[:req].new(env)
  @res = settings[:res].new(settings[:default_headers].dup)

  # This `catch` statement will either receive a
  # rack response tuple via a `halt`, or will
  # fall back to issuing a 404.
  #
  # When it `catch`es a throw, the return value
  # of this whole `call!` method will be the
  # rack response tuple, which is exactly what we want.
  catch(:halt) do
    instance_eval(&@blk)

    res.status = 404
    res.finish
  end
end

#defaultObject

Syntactic sugar for providing catch-all matches.

Examples:

on default do
  res.write "404"
end


293
294
295
# File 'lib/cuba.rb', line 293

def default
  true
end

#deleteObject



320
# File 'lib/cuba.rb', line 320

def delete; req.delete? end

#extension(ext = "\\w+") ⇒ Object

A matcher for files with a certain extension.

Examples:

# PATH_INFO=/style/app.css
on "style", extension("css") do |file|
  res.write file # writes app
end


239
240
241
# File 'lib/cuba.rb', line 239

def extension(ext = "\\w+")
  lambda { consume("([^\\/]+?)\.#{ext}\\z") }
end

#getObject

Syntatic sugar for providing HTTP Verb matching.

Examples:

on get, "signup" do
end

on post, "signup" do
end


317
# File 'lib/cuba.rb', line 317

def get;    req.get?    end

#halt(response) ⇒ Object



339
340
341
# File 'lib/cuba.rb', line 339

def halt(response)
  throw :halt, response
end

#header(key) ⇒ Object



255
256
257
# File 'lib/cuba.rb', line 255

def header(key)
  lambda { env[key.upcase.tr("-","_")] }
end

#host(hostname) ⇒ Object

Useful for matching against the request host (i.e. HTTP_HOST).

Examples:

on host("account1.example.com"), "api" do
  res.write "You have reached the API of account1."
end


265
266
267
# File 'lib/cuba.rb', line 265

def host(hostname)
  hostname === req.host
end

#match(matcher, segment = "([^\\/]+)") ⇒ Object



221
222
223
224
225
226
227
228
229
230
# File 'lib/cuba.rb', line 221

def match(matcher, segment = "([^\\/]+)")
  case matcher
  when String then consume(matcher.gsub(/:\w+/, segment))
  when Regexp then consume(matcher)
  when Symbol then consume(segment)
  when Proc   then matcher.call
  else
    matcher
  end
end

#on(*args, &block) ⇒ Object

The heart of the path / verb / any condition matching.

Examples:


on get do
  res.write "GET"
end

on get, "signup" do
  res.write "Signup"
end

on "user/:id" do |uid|
  res.write "User: #{uid}"
end

on "styles", extension("css") do |file|
  res.write render("styles/#{file}.sass")
end


160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/cuba.rb', line 160

def on(*args, &block)
  try do
    # For every block, we make sure to reset captures so that
    # nesting matchers won't mess with each other's captures.
    @captures = []

    # We stop evaluation of this entire matcher unless
    # each and every `arg` defined for this matcher evaluates
    # to a non-false value.
    #
    # Short circuit examples:
    #    on true, false do
    #
    #    # PATH_INFO=/user
    #    on true, "signup"
    return unless args.all? { |arg| match(arg) }

    # The captures we yield here were generated and assembled
    # by evaluating each of the `arg`s above. Most of these
    # are carried out by #consume.
    yield(*captures)

    if res.status.nil?
      if res.body.empty?
        res.status = 404
      else
        res.headers["Content-Type"] ||= "text/html; charset=utf-8"
        res.status = 200
      end
    end

    halt(res.finish)
  end
end

#param(key) ⇒ Object

Used to ensure that certain request parameters are present. Acts like a precondition / assertion for your route.

Examples:

# POST with data like user[fname]=John&user[lname]=Doe
on "signup", param("user") do |atts|
  User.create(atts)
end


251
252
253
# File 'lib/cuba.rb', line 251

def param(key)
  lambda { captures << req[key] unless req[key].to_s.empty? }
end

#postObject



318
# File 'lib/cuba.rb', line 318

def post;   req.post?   end

#putObject



319
# File 'lib/cuba.rb', line 319

def put;    req.put?    end

#rootObject

Access the root of the application.

Examples:


# GET /
on root do
  res.write "Home"
end


305
306
307
# File 'lib/cuba.rb', line 305

def root
  env["PATH_INFO"] == "/" || env["PATH_INFO"] == ""
end

#run(app) ⇒ Object

If you want to halt the processing of an existing handler and continue it via a different handler.

Examples:

def redirect(*args)
  run Cuba.new { on(default) { res.redirect(*args) }}
end

on "account" do
  redirect "/login" unless session["uid"]

  res.write "Super secure account info."
end


335
336
337
# File 'lib/cuba.rb', line 335

def run(app)
  halt app.call(req.env)
end

#sessionObject



134
135
136
137
138
# File 'lib/cuba.rb', line 134

def session
  env["rack.session"] || raise(RuntimeError,
    "You're missing a session handler. You can get started " +
    "by adding Cuba.use Rack::Session::Cookie")
end

#settingsObject



106
107
108
# File 'lib/cuba.rb', line 106

def settings
  self.class.settings
end

#varsObject

Returns a hash with the information set by the #with method.

with(role: "admin", site: "main") do
  on default do
    res.write(vars.inspect)
  end
end
# => '{:role=>"admin", :site=>"main"}'


382
383
384
# File 'lib/cuba.rb', line 382

def vars
  env["cuba.vars"] ||= {}
end

#with(dict = {}) ⇒ Object

Adds ability to pass information to a nested Cuba application. It receives two parameters: a hash that represents the passed information and a block. The #vars method is used to retrieve a hash with the passed information.

class Platforms < Cuba
  define do
    platform = vars[:platform]

    on default do
      res.write(platform) # => "heroku" or "salesforce"
    end
  end
end

Cuba.define do
  on "(heroku|salesforce)" do |platform|
    with(platform: platform) do
      run(Platforms)
    end
  end
end


366
367
368
369
370
371
# File 'lib/cuba.rb', line 366

def with(dict = {})
  old, env["cuba.vars"] = vars, vars.merge(dict)
  yield
ensure
  env["cuba.vars"] = old
end