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

Constant Summary collapse

SLASH =
"/".freeze
EMPTY =
"".freeze
SEGMENT =
"([^\\/]+)".freeze
DEFAULT =
"text/html; charset=utf-8".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(&blk) ⇒ Cuba

Returns a new instance of Cuba.



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

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

Instance Attribute Details

#capturesObject (readonly)

Returns the value of attribute captures.



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

def captures
  @captures
end

#envObject (readonly)

Returns the value of attribute env.



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

def env
  @env
end

#reqObject (readonly)

Returns the value of attribute req.



104
105
106
# File 'lib/cuba.rb', line 104

def req
  @req
end

#resObject (readonly)

Returns the value of attribute res.



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

def res
  @res
end

Class Method Details

.appObject



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

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

.call(env) ⇒ Object



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

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

.deepclone(obj) ⇒ Object



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

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

.define(&block) ⇒ Object



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

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

.inherited(child) ⇒ Object



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

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

.plugin(mixin) ⇒ Object



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

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

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

.prototypeObject



76
77
78
# File 'lib/cuba.rb', line 76

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

.reset!Object



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

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

.settingsObject



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

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

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



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

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


288
289
290
291
292
293
294
295
296
# File 'lib/cuba.rb', line 288

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

    if accept.any? { |s| s.strip == mimetype }
      res[Rack::CONTENT_TYPE] = mimetype
    end
  end
end

#call(env) ⇒ Object



117
118
119
# File 'lib/cuba.rb', line 117

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

#call!(env) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/cuba.rb', line 121

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)

    not_found
    res.finish
  end
end

#defaultObject

Syntactic sugar for providing catch-all matches.

Examples:

on default do
  res.write "404"
end


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

def default
  true
end

#deleteObject



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

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


246
247
248
# File 'lib/cuba.rb', line 246

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


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

def get;     req.get?     end

#halt(response) ⇒ Object



356
357
358
# File 'lib/cuba.rb', line 356

def halt(response)
  throw :halt, response
end

#headObject



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

def head;    req.head?    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


276
277
278
# File 'lib/cuba.rb', line 276

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


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

def link;    req.link?    end

#match(matcher, segment = SEGMENT) ⇒ Object



228
229
230
231
232
233
234
235
236
237
# File 'lib/cuba.rb', line 228

def match(matcher, segment = 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

#not_foundObject



403
404
405
# File 'lib/cuba.rb', line 403

def not_found
  res.status = 404
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


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
194
195
196
197
198
199
200
# File 'lib/cuba.rb', line 167

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?
        not_found
      else
        res.headers[Rack::CONTENT_TYPE] ||= DEFAULT
        res.status = 200
      end
    end

    halt(res.finish)
  end
end

#optionsObject



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

def options; req.options? end

#param(key, default = nil) ⇒ Object

Ensures that certain request parameters are present. Acts like a precondition / assertion for your route. A default value can be provided as a second argument. In that case, it always matches and the result is either the parameter or the default value.

Examples:

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

on "login", param("username", "guest") do |username|
  # If not provided, username == "guest"
end


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

def param(key, default = nil)
  value = req[key] || default

  lambda { captures << value unless value.to_s.empty? }
end

#patchObject



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

def patch;   req.patch?   end

#postObject



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

def post;    req.post?    end

#putObject



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

def put;     req.put?     end

#rootObject

Access the root of the application.

Examples:


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


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

def root
  env[Rack::PATH_INFO] == SLASH || env[Rack::PATH_INFO] == EMPTY
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


352
353
354
# File 'lib/cuba.rb', line 352

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

#sessionObject



141
142
143
144
145
# File 'lib/cuba.rb', line 141

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



113
114
115
# File 'lib/cuba.rb', line 113

def settings
  self.class.settings
end

#traceObject



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

def trace;   req.trace?   end


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

def unlink;  req.unlink?  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"}'


399
400
401
# File 'lib/cuba.rb', line 399

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


383
384
385
386
387
388
# File 'lib/cuba.rb', line 383

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