Skip to content

Commit

Permalink
Merged from master.
Browse files Browse the repository at this point in the history
  • Loading branch information
dblock committed Jul 21, 2011
2 parents fec39fd + f1ea3d7 commit 7b2da89
Show file tree
Hide file tree
Showing 18 changed files with 444 additions and 221 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ pkg
.rvmrc
.bundle
.yardoc/*
dist

## PROJECT::SPECIFIC
41 changes: 21 additions & 20 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
grape (0.1.3)
grape (0.1.5)
multi_json
multi_xml
rack
Expand All @@ -11,41 +11,42 @@ PATH
GEM
remote: http://rubygems.org/
specs:
ZenTest (4.5.0)
diff-lcs (1.1.2)
json_pure (1.4.3)
json_pure (1.5.2)
maruku (0.6.0)
syntax (>= 1.0.0)
mg (0.0.8)
rake
multi_json (0.0.5)
multi_json (1.0.3)
multi_xml (0.2.2)
rack (1.2.1)
rack-jsonp (1.1.0)
rack (1.3.0)
rack-jsonp (1.2.0)
rack
rack-mount (0.7.1)
rack-mount (0.8.1)
rack (>= 1.0.0)
rack-test (0.5.4)
rack-test (0.6.0)
rack (>= 1.0)
rake (0.8.7)
rspec (2.5.0)
rspec-core (~> 2.5.0)
rspec-expectations (~> 2.5.0)
rspec-mocks (~> 2.5.0)
rspec-core (2.5.1)
rspec-expectations (2.5.0)
rake (0.9.2)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.5.0)
rspec-mocks (2.6.0)
syntax (1.0.0)
yard (0.6.1)
yard (0.7.1)

PLATFORMS
ruby

DEPENDENCIES
ZenTest
bundler
grape!
json_pure
maruku
mg
rack-test
rspec (~> 2.5.0)
rake
rspec (~> 2.6.0)
yard
12 changes: 5 additions & 7 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Grape
[![Build Status](http://travis-ci.org/intridea/grape.png)](http://travis-ci.org/intridea/grape)

Grape is a REST-like API micro-framework for Ruby. It is built to complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily provide APIs. It has built-in support for common conventions such as multiple formats, subdomain/prefix restriction, and versioning.

Expand Down Expand Up @@ -74,15 +75,12 @@ You can also return JSON formatted objects explicitly by raising error! and pass

## Exception Handling

By default Grape does not catch all unexpected exceptions. This means that the web server will handle the error and render the default error page as a result. It is possible to trap all exceptions by setting `rescue_all_errors true` instead. You may change the error format to JSON by using `error_format :json` and set the default error status to 200 with `default_error_status 200`. You may also include the complete backtrace of the exception with `rescue_with_backtrace true` either as text (for the :txt format) or as a :backtrace field in the json (for the :json format).
Grape can be told to rescue certain (or all) exceptions in your
application and instead display them in text or json form. To do this,
you simply use the `rescue_from` method inside your API declaration:

class Twitter::API < Grape::API
rescue_all_errors true
rescue_with_backtrace true
error_format :json
default_error_status 200

# api methods
rescue_from ArgumentError, NotImplementedError # :all for all errors
end

## Note on Patches/Pull Requests
Expand Down
3 changes: 1 addition & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ require 'rubygems'
require 'bundler'
Bundler.setup :default, :test, :development

require 'mg'
MG.new('grape.gemspec')
Bundler::GemHelper.install_tasks

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
Expand Down
6 changes: 4 additions & 2 deletions grape.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'multi_json'
s.add_runtime_dependency 'multi_xml'

s.add_development_dependency 'mg'
s.add_development_dependency 'rake'
s.add_development_dependency 'maruku'
s.add_development_dependency 'yard'
s.add_development_dependency 'rack-test'
s.add_development_dependency 'rspec', '~> 2.5.0'
s.add_development_dependency 'rspec', '~> 2.6.0'
s.add_development_dependency 'json_pure'
s.add_development_dependency 'ZenTest'
s.add_development_dependency 'bundler'

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
Expand Down
1 change: 1 addition & 0 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module Middleware
module Auth
autoload :OAuth2, 'grape/middleware/auth/oauth2'
autoload :Basic, 'grape/middleware/auth/basic'
autoload :Digest, 'grape/middleware/auth/digest'
end
end
end
Expand Down
47 changes: 36 additions & 11 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'rack/mount'
require 'rack/auth/basic'
require 'rack/auth/digest/md5'
require 'logger'

module Grape
Expand Down Expand Up @@ -90,14 +91,26 @@ def default_error_status(new_status = nil)
new_status ? set(:default_error_status, new_status) : settings[:default_error_status]
end

# Specify whether to rescue all errors.
def rescue_all_errors(new_value = true)
set(:rescue_all_errors, new_value)
end

# Specify whether to include error backtrace with errors.
def rescue_with_backtrace(new_value = true)
set(:rescue_with_backtrace, new_value)
# Allows you to rescue certain exceptions that occur to return
# a grape error rather than raising all the way to the
# server level.
#
# @example Rescue from custom exceptions
# class ExampleAPI < Grape::API
# class CustomError < StandardError; end
#
# rescue_from CustomError
# end
#
# @overload rescue_from(*exception_classes, options = {})
# @param [Array] exception_classes A list of classes that you want to rescue, or
# the symbol :all to rescue from all exceptions.
# @param [Hash] options Options for the rescue usage.
# @option options [Boolean] :backtrace Include a backtrace in the rescue response.
def rescue_from(*args)
set(:rescue_options, args.pop) if args.last.is_a?(Hash)
set(:rescue_all, true) and return if args.include?(:all)
set(:rescued_errors, args)
end

# Add helper methods that will be accessible from any
Expand Down Expand Up @@ -126,7 +139,7 @@ def helpers(&block)
end

# Add an authentication type to the API. Currently
# only `:http_basic` is supported.
# only `:http_basic`, `:http_digest` and `:oauth2` are supported.
def auth(type = nil, options = {}, &block)
if type
set(:auth, {:type => type.to_sym, :proc => block}.merge(options))
Expand All @@ -143,6 +156,12 @@ def http_basic(options = {}, &block)
options[:realm] ||= "API Authorization"
auth :http_basic, options, &block
end

def http_digest(options = {}, &block)
options[:realm] ||= "API Authorization"
options[:opaque] ||= "secret"
auth :http_digest, options, &block
end

# Defines a route that will be recognized
# by the Grape API.
Expand All @@ -166,7 +185,7 @@ def route(methods, paths, &block)

methods.each do |method|
paths.each do |path|
path = Rack::Mount::Strexp.compile(compile_path(path), options, ['/'], true)
path = Rack::Mount::Strexp.compile(compile_path(path), options, %w( / . ? ), true)
route_set.add_route(endpoint,
:path_info => path,
:request_method => (method.to_s.upcase unless method == :any)
Expand Down Expand Up @@ -238,8 +257,14 @@ def nest(*blocks, &block)

def build_endpoint(&block)
b = Rack::Builder.new
b.use Grape::Middleware::Error, :default_status => settings[:default_error_status] || 403, :rescue => settings[:rescue_all_errors], :format => settings[:error_format] || :txt, :backtrace => settings[:rescue_with_backtrace]
b.use Grape::Middleware::Error,
:default_status => settings[:default_error_status] || 403,
:rescue_all => settings[:rescue_all],
:rescued_errors => settings[:rescued_errors],
:format => settings[:error_format] || :txt,
:rescue_options => settings[:rescue_options]
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest
b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
b.use Grape::Middleware::Formatter, :default_format => default_format || :json
Expand Down
30 changes: 30 additions & 0 deletions lib/grape/middleware/auth/digest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'rack/auth/digest/md5'

module Grape
module Middleware
module Auth
class Digest < Grape::Middleware::Base
attr_reader :authenticator

def initialize(app, options = {}, &authenticator)
super(app, options)
@authenticator = authenticator
end

def digest_request
Rack::Auth::Digest::Request.new(env)
end

def credentials
digest_request.provided?? digest_request.credentials : [nil, nil]
end

def before
unless authenticator.call(*credentials)
throw :error, :status => 401, :message => "API Authorization Failed."
end
end
end
end
end
end
48 changes: 33 additions & 15 deletions lib/grape/middleware/auth/oauth2.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
module Grape::Middleware::Auth
# OAuth 2.0 authorization for Grape APIs.
class OAuth2 < Grape::Middleware::Base
def default_options
{
:token_class => 'AccessToken',
:realm => 'OAuth API'
:realm => 'OAuth API',
:parameter => %w(bearer_token oauth_token),
:accepted_headers => %w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION),
:header => [/Bearer (.*)/i, /OAuth (.*)/i]
}
end

def before
if request['oauth_token']
verify_token(request['oauth_token'])
elsif env['Authorization'] && t = parse_authorization_header
verify_token(t)
verify_token(token_parameter || token_header)
end

def token_parameter
Array(options[:parameter]).each do |p|
return request[p] if request[p]
end
nil
end

def token_header
return false unless authorization_header
Array(options[:header]).each do |regexp|
if authorization_header =~ regexp
return $1
end
end
nil
end

def authorization_header
options[:accepted_headers].each do |head|
return env[head] if env[head]
end
nil
end

def token_class
Expand All @@ -21,10 +45,10 @@ def token_class

def verify_token(token)
if token = token_class.verify(token)
if token.expired?
if token.respond_to?(:expired?) && token.expired?
error_out(401, 'expired_token')
else
if token.permission_for?(env)
if !token.respond_to?(:permission_for?) || token.permission_for?(env)
env['api.token'] = token
else
error_out(403, 'insufficient_scope')
Expand All @@ -35,15 +59,9 @@ def verify_token(token)
end
end

def parse_authorization_header
if env['Authorization'] =~ /oauth (.*)/i
$1
end
end

def error_out(status, error)
throw :error, {
:message => 'The token provided has expired.',
:message => error,
:status => status,
:headers => {
'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
Expand All @@ -52,4 +70,4 @@ def error_out(status, error)
end
end
end


Loading

0 comments on commit 7b2da89

Please sign in to comment.