Skip to content

Commit c6cd3a1

Browse files
committed
Merge pull request #703 from dspaeth-faber/probosal_for_auth_middelware_refactoring
Make auth middleware extendable
2 parents 1fe9e43 + ac60d6c commit c6cd3a1

16 files changed

+221
-380
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
* [#691](https://github.com/intridea/grape/issues/691): Added `at_least_one_of` parameter validator - [@dblock](https://github.com/dblock).
55
* [#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).
66
* [#698](https://github.com/intridea/grape/pull/698): `error!` sets `status` for `Endpoint` too - [@dspaeth-faber](https://github.com/dspaeth-faber).
7+
* [#703](https://github.com/intridea/grape/pull/703): Added support for Auth-Middleware extension - [@dspaeth-faber](https://github.com/dspaeth-faber).
8+
* [#703](https://github.com/intridea/grape/pull/703): Removed `Grape::Middleware::Auth::Basic` - [@dspaeth-faber](https://github.com/dspaeth-faber).
9+
* [#703](https://github.com/intridea/grape/pull/703): Removed `Grape::Middleware::Auth::Digest` - [@dspaeth-faber](https://github.com/dspaeth-faber).
10+
* [#703](https://github.com/intridea/grape/pull/703): Removed `Grape::Middleware::Auth::OAuth2` - [@dspaeth-faber](https://github.com/dspaeth-faber).
711
* Your contribution here.
812

913
0.8.0 (7/10/2014)

README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -1468,7 +1468,8 @@ formatter.
14681468

14691469
### Basic and Digest Auth
14701470

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

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

1488+
### Register custom middleware for authentication
1489+
1490+
Grape can use custom Middleware for authentication. How to implement these
1491+
Middleware have a look at `Rack::Auth::Basic` or similar implementations.
1492+
1493+
1494+
For registering a Middlewar you need the following options:
1495+
1496+
* `label` - the name for your authenticator to use it later
1497+
* `MiddlewareClass` - the MiddlewareClass to use for authentication
1498+
* `option_lookup_proc` - A Proc with one Argument to lookup the options at
1499+
runtime (return value is an `Array` as Paramter for the Middleware).
1500+
1501+
Example:
1502+
1503+
```ruby
1504+
1505+
Grape::Middleware::Auth::Strategies.add(:my_auth, AuthMiddleware, ->(options) { [options[:realm]] } )
1506+
1507+
1508+
auth :my_auth ,{ real: 'Test Api'} do |credentials|
1509+
# lookup the user's password here
1510+
{ 'user1' => 'password1' }[username]
1511+
end
1512+
1513+
```
1514+
14871515
Use [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2) for OAuth2 support.
14881516

14891517
## Describing and Inspecting an API

lib/grape.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ module Middleware
7777
autoload :Error, 'grape/middleware/error'
7878

7979
module Auth
80-
autoload :OAuth2, 'grape/middleware/auth/oauth2'
81-
autoload :Base, 'grape/middleware/auth/base'
82-
autoload :Basic, 'grape/middleware/auth/basic'
83-
autoload :Digest, 'grape/middleware/auth/digest'
80+
autoload :Base, 'grape/middleware/auth/base'
81+
autoload :DSL, 'grape/middleware/auth/dsl'
82+
autoload :StrategyInfo, 'grape/middleware/auth/strategy_info'
83+
autoload :Strategies, 'grape/middleware/auth/strategies'
8484
end
8585

8686
module Versioner

lib/grape/api.rb

+1-25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Grape
44
# class in order to build an API.
55
class API
66
extend Validations::ClassMethods
7+
extend Grape::Middleware::Auth::DSL
78

89
class << self
910
attr_reader :endpoints, :instance, :routes, :route_set, :settings, :versions
@@ -301,31 +302,6 @@ def helpers(new_mod = nil, &block)
301302
end
302303
end
303304

304-
# Add an authentication type to the API. Currently
305-
# only `:http_basic`, `:http_digest` and `:oauth2` are supported.
306-
def auth(type = nil, options = {}, &block)
307-
if type
308-
set(:auth, { type: type.to_sym, proc: block }.merge(options))
309-
else
310-
settings[:auth]
311-
end
312-
end
313-
314-
# Add HTTP Basic authorization to the API.
315-
#
316-
# @param [Hash] options A hash of options.
317-
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
318-
def http_basic(options = {}, &block)
319-
options[:realm] ||= "API Authorization"
320-
auth :http_basic, options, &block
321-
end
322-
323-
def http_digest(options = {}, &block)
324-
options[:realm] ||= "API Authorization"
325-
options[:opaque] ||= "secret"
326-
auth :http_digest, options, &block
327-
end
328-
329305
def mount(mounts)
330306
mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
331307
mounts.each_pair do |app, path|

lib/grape/endpoint.rb

-16
Original file line numberDiff line numberDiff line change
@@ -456,22 +456,6 @@ def build_middleware
456456
end
457457
end
458458

459-
if settings[:auth]
460-
auth_proc = settings[:auth][:proc]
461-
auth_proc_context = self
462-
auth_middleware = {
463-
http_basic: { class: Rack::Auth::Basic, args: [settings[:auth][:realm]] },
464-
http_digest: { class: Rack::Auth::Digest::MD5, args: [settings[:auth][:realm], settings[:auth][:opaque]] }
465-
}[settings[:auth][:type]]
466-
467-
# evaluate auth proc in context of endpoint
468-
if auth_middleware
469-
b.use auth_middleware[:class], *auth_middleware[:args] do |*args|
470-
auth_proc_context.instance_exec(*args, &auth_proc)
471-
end
472-
end
473-
end
474-
475459
if settings[:version]
476460
b.use Grape::Middleware::Versioner.using(settings[:version_options][:using]),
477461
versions: settings[:version] ? settings[:version].flatten : nil,

lib/grape/middleware/auth/base.rb

+28-12
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,41 @@
33
module Grape
44
module Middleware
55
module Auth
6-
class Base < Grape::Middleware::Base
7-
attr_reader :authenticator
6+
class Base
7+
attr_accessor :options, :app, :env
88

9-
def initialize(app, options = {}, &authenticator)
10-
super(app, options)
11-
@authenticator = authenticator
9+
def initialize(app, options = {})
10+
@app = app
11+
@options = options || {}
1212
end
1313

14-
def base_request
15-
raise NotImplementedError, "You must implement base_request."
14+
def context
15+
env['api.endpoint']
1616
end
1717

18-
def credentials
19-
base_request.provided? ? base_request.credentials : [nil, nil]
18+
def call(env)
19+
dup._call(env)
2020
end
2121

22-
def before
23-
unless authenticator.call(*credentials)
24-
throw :error, status: 401, message: "API Authorization Failed."
22+
def _call(env)
23+
self.env = env
24+
25+
if options.key?(:type)
26+
auth_proc = options[:proc]
27+
auth_proc_context = context
28+
29+
strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
30+
31+
throw(:error, status: 401, message: "API Authorization Failed.") unless strategy_info.present?
32+
33+
strategy = strategy_info.create(@app, options) do |*args|
34+
auth_proc_context.instance_exec(*args, &auth_proc)
35+
end
36+
37+
strategy.call(env)
38+
39+
else
40+
app.call(env)
2541
end
2642
end
2743
end

lib/grape/middleware/auth/basic.rb

-13
This file was deleted.

lib/grape/middleware/auth/digest.rb

-13
This file was deleted.

lib/grape/middleware/auth/dsl.rb

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
require 'rack/auth/basic'
2+
3+
module Grape
4+
module Middleware
5+
module Auth
6+
module DSL
7+
# Add an authentication type to the API. Currently
8+
# only `:http_basic`, `:http_digest` are supported.
9+
def auth(type = nil, options = {}, &block)
10+
if type
11+
set(:auth, { type: type.to_sym, proc: block }.merge(options))
12+
use Grape::Middleware::Auth::Base, settings[:auth]
13+
else
14+
settings[:auth]
15+
end
16+
end
17+
18+
# Add HTTP Basic authorization to the API.
19+
#
20+
# @param [Hash] options A hash of options.
21+
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
22+
def http_basic(options = {}, &block)
23+
options[:realm] ||= "API Authorization"
24+
auth :http_basic, options, &block
25+
end
26+
27+
def http_digest(options = {}, &block)
28+
options[:realm] ||= "API Authorization"
29+
options[:opaque] ||= "secret"
30+
auth :http_digest, options, &block
31+
end
32+
end
33+
end
34+
end
35+
end

lib/grape/middleware/auth/oauth2.rb

-83
This file was deleted.
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module Grape
2+
module Middleware
3+
module Auth
4+
module Strategies
5+
module_function
6+
7+
def add(label, strategy, option_fetcher = ->(_) { [] })
8+
auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)
9+
end
10+
11+
def auth_strategies
12+
@auth_strategies ||= {
13+
http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) {[settings[:realm]] }),
14+
http_digest: StrategyInfo.new(Rack::Auth::Digest::MD5, ->(settings) { [settings[:realm], settings[:opaque]] })
15+
}
16+
end
17+
18+
def [](label)
19+
auth_strategies[label]
20+
end
21+
end
22+
end
23+
end
24+
end
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Grape
2+
module Middleware
3+
module Auth
4+
StrategyInfo = Struct.new(:auth_class, :settings_fetcher) do
5+
6+
def create(app, options, &block)
7+
strategy_args = settings_fetcher.call(options)
8+
9+
auth_class.new(app, *strategy_args, &block)
10+
end
11+
12+
end
13+
end
14+
end
15+
end

0 commit comments

Comments
 (0)