Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make auth middleware extendable #703

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
* [#691](https://github.com/intridea/grape/issues/691): Added `at_least_one_of` parameter validator - [@dblock](https://github.com/dblock).
* [#687](https://github.com/intridea/grape/pull/687): Fix: `mutually_exclusive` and `exactly_one_of` validation error messages now label parameters as strings, consistently with `requires` and `optional` - [@dblock](https://github.com/dblock).
* [#698](https://github.com/intridea/grape/pull/698): `error!` sets `status` for `Endpoint` too - [@dspaeth-faber](https://github.com/dspaeth-faber).
* [#703](https://github.com/intridea/grape/pull/703): Added support for Auth-Middleware extension - [@dspaeth-faber](https://github.com/dspaeth-faber).
* [#703](https://github.com/intridea/grape/pull/703): Removed `Grape::Middleware::Auth::Basic` - [@dspaeth-faber](https://github.com/dspaeth-faber).
* [#703](https://github.com/intridea/grape/pull/703): Removed `Grape::Middleware::Auth::Digest` - [@dspaeth-faber](https://github.com/dspaeth-faber).
* [#703](https://github.com/intridea/grape/pull/703): Removed `Grape::Middleware::Auth::OAuth2` - [@dspaeth-faber](https://github.com/dspaeth-faber).
* Your contribution here.

0.8.0 (7/10/2014)
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1468,7 +1468,8 @@ formatter.

### Basic and Digest Auth

Grape has built-in Basic and Digest authentication.
Grape has built-in Basic and Digest authentication (the given `block`
is executed in the context of the current `Endpoint`).

```ruby
http_basic do |username, password|
Expand All @@ -1484,6 +1485,33 @@ http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
end
```

### Register custom middleware for authentication

Grape can use custom Middleware for authentication. How to implement these
Middleware have a look at `Rack::Auth::Basic` or similar implementations.


For registering a Middlewar you need the following options:

* `label` - the name for your authenticator to use it later
* `MiddlewareClass` - the MiddlewareClass to use for authentication
* `option_lookup_proc` - A Proc with one Argument to lookup the options at
runtime (return value is an `Array` as Paramter for the Middleware).

Example:

```ruby

Grape::Middleware::Auth::Strategies.add(:my_auth, AuthMiddleware, ->(options) { [options[:realm]] } )


auth :my_auth ,{ real: 'Test Api'} do |credentials|
# lookup the user's password here
{ 'user1' => 'password1' }[username]
end

```

Use [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2) for OAuth2 support.

## Describing and Inspecting an API
Expand Down
8 changes: 4 additions & 4 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ module Middleware
autoload :Error, 'grape/middleware/error'

module Auth
autoload :OAuth2, 'grape/middleware/auth/oauth2'
autoload :Base, 'grape/middleware/auth/base'
autoload :Basic, 'grape/middleware/auth/basic'
autoload :Digest, 'grape/middleware/auth/digest'
autoload :Base, 'grape/middleware/auth/base'
autoload :DSL, 'grape/middleware/auth/dsl'
autoload :StrategyInfo, 'grape/middleware/auth/strategy_info'
autoload :Strategies, 'grape/middleware/auth/strategies'
end

module Versioner
Expand Down
26 changes: 1 addition & 25 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Grape
# class in order to build an API.
class API
extend Validations::ClassMethods
extend Grape::Middleware::Auth::DSL

class << self
attr_reader :endpoints, :instance, :routes, :route_set, :settings, :versions
Expand Down Expand Up @@ -301,31 +302,6 @@ def helpers(new_mod = nil, &block)
end
end

# Add an authentication type to the API. Currently
# 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))
else
settings[:auth]
end
end

# Add HTTP Basic authorization to the API.
#
# @param [Hash] options A hash of options.
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
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

def mount(mounts)
mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
mounts.each_pair do |app, path|
Expand Down
16 changes: 0 additions & 16 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -456,22 +456,6 @@ def build_middleware
end
end

if settings[:auth]
auth_proc = settings[:auth][:proc]
auth_proc_context = self
auth_middleware = {
http_basic: { class: Rack::Auth::Basic, args: [settings[:auth][:realm]] },
http_digest: { class: Rack::Auth::Digest::MD5, args: [settings[:auth][:realm], settings[:auth][:opaque]] }
}[settings[:auth][:type]]

# evaluate auth proc in context of endpoint
if auth_middleware
b.use auth_middleware[:class], *auth_middleware[:args] do |*args|
auth_proc_context.instance_exec(*args, &auth_proc)
end
end
end

if settings[:version]
b.use Grape::Middleware::Versioner.using(settings[:version_options][:using]),
versions: settings[:version] ? settings[:version].flatten : nil,
Expand Down
40 changes: 28 additions & 12 deletions lib/grape/middleware/auth/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,41 @@
module Grape
module Middleware
module Auth
class Base < Grape::Middleware::Base
attr_reader :authenticator
class Base
attr_accessor :options, :app, :env

def initialize(app, options = {}, &authenticator)
super(app, options)
@authenticator = authenticator
def initialize(app, options = {})
@app = app
@options = options || {}
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the German mittelware :)

More seriously, lets think about whether this auth-specific stuff can be taken out altogether?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One can't hide his heritage ;)

Warden uses it's strategy module to register new strategies.

Grape has no global config right?


def base_request
raise NotImplementedError, "You must implement base_request."
def context
env['api.endpoint']
end

def credentials
base_request.provided? ? base_request.credentials : [nil, nil]
def call(env)
dup._call(env)
end

def before
unless authenticator.call(*credentials)
throw :error, status: 401, message: "API Authorization Failed."
def _call(env)
self.env = env

if options.key?(:type)
auth_proc = options[:proc]
auth_proc_context = context

strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]

throw(:error, status: 401, message: "API Authorization Failed.") unless strategy_info.present?

strategy = strategy_info.create(@app, options) do |*args|
auth_proc_context.instance_exec(*args, &auth_proc)
end

strategy.call(env)

else
app.call(env)
end
end
end
Expand Down
13 changes: 0 additions & 13 deletions lib/grape/middleware/auth/basic.rb

This file was deleted.

13 changes: 0 additions & 13 deletions lib/grape/middleware/auth/digest.rb

This file was deleted.

35 changes: 35 additions & 0 deletions lib/grape/middleware/auth/dsl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'rack/auth/basic'

module Grape
module Middleware
module Auth
module DSL
# Add an authentication type to the API. Currently
# only `:http_basic`, `:http_digest` are supported.
def auth(type = nil, options = {}, &block)
if type
set(:auth, { type: type.to_sym, proc: block }.merge(options))
use Grape::Middleware::Auth::Base, settings[:auth]
else
settings[:auth]
end
end

# Add HTTP Basic authorization to the API.
#
# @param [Hash] options A hash of options.
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
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
end
end
end
end
83 changes: 0 additions & 83 deletions lib/grape/middleware/auth/oauth2.rb

This file was deleted.

24 changes: 24 additions & 0 deletions lib/grape/middleware/auth/strategies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Grape
module Middleware
module Auth
module Strategies
module_function

def add(label, strategy, option_fetcher = ->(_) { [] })
auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)
end

def auth_strategies
@auth_strategies ||= {
http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) {[settings[:realm]] }),
http_digest: StrategyInfo.new(Rack::Auth::Digest::MD5, ->(settings) { [settings[:realm], settings[:opaque]] })
}
end

def [](label)
auth_strategies[label]
end
end
end
end
end
15 changes: 15 additions & 0 deletions lib/grape/middleware/auth/strategy_info.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Grape
module Middleware
module Auth
StrategyInfo = Struct.new(:auth_class, :settings_fetcher) do

def create(app, options, &block)
strategy_args = settings_fetcher.call(options)

auth_class.new(app, *strategy_args, &block)
end

end
end
end
end
Loading