hadley 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.yardopts +1 -0
- data/LICENSE.txt +7 -0
- data/README.rdoc +45 -0
- data/config.ru +5 -5
- data/hadley.gemspec +2 -0
- data/lib/hadley.rb +4 -1
- data/lib/hadley/authz.rb +10 -40
- data/lib/hadley/authz/basic.rb +63 -42
- data/lib/hadley/authz/bearer.rb +67 -42
- data/lib/hadley/authz/strategy.rb +11 -0
- data/lib/hadley/authz/strategy_builder.rb +51 -0
- data/lib/hadley/config.rb +47 -5
- data/lib/hadley/middleware.rb +10 -6
- data/lib/hadley/token_store.rb +39 -4
- data/lib/hadley/utils.rb +11 -2
- metadata +40 -12
data/.gitignore
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--protected lib/**/*.rb ext/**/*.c README.rdoc
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 Sean M. Duncan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
== Welcome to Hadley
|
2
|
+
|
3
|
+
Hadley is rack middleware built on top of the excellent security authentication middleware warden. Hadley enables
|
4
|
+
Rack-based web applications to easily become AFID protected resource servers.
|
5
|
+
|
6
|
+
|
7
|
+
== Getting Started
|
8
|
+
|
9
|
+
Rails:
|
10
|
+
|
11
|
+
1. Add <tt>gem 'hadley'</tt> to your Gemfile
|
12
|
+
|
13
|
+
2. Run <tt>bundle</tt> from your project root
|
14
|
+
|
15
|
+
3. Run <tt>touch config/initializers/hadley.rb</tt> from your project root
|
16
|
+
|
17
|
+
4. Add warden and hadley to your middleware stack by opening <tt>config/initializers/hadlery.rb</tt> in your favorite text editor and adding the following:
|
18
|
+
|
19
|
+
token_store = Hadley::TokenStore.new(Rails.cache)
|
20
|
+
|
21
|
+
MyApp::Application.config.middleware.insert_after ActionDispatch::Session::CookieStore, Warden::Manager do |manager|
|
22
|
+
# setup authentication for the afid server to provision and revoke access tokens
|
23
|
+
manager.basic(:server) do |basic|
|
24
|
+
basic.hash_credentials true
|
25
|
+
basic.lookup do |id, secret|
|
26
|
+
[ id, secret ] == [ 'my_hashed_id', 'my_hashed_secret' ] ? id : nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
# setup authentication for afid clients to authenticate in anonymous mode (client_credentials grant type in OAuth2
|
30
|
+
# parlance)
|
31
|
+
manager.bearer(:client) do |bearer|
|
32
|
+
bearer.token_store token_store
|
33
|
+
bearer.anonymous_allowed true
|
34
|
+
end
|
35
|
+
# setup authentication for afid clients to access apis on behalf of a particular user (authorization_grant grant
|
36
|
+
# type in OAuth2 parlance)
|
37
|
+
manager.bearer(:user) do |bearer|
|
38
|
+
bearer.token_store token_store
|
39
|
+
bearer.anonymous_allowed false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
MyApp::Application.config.middleware.insert_after Warden::Manager, Hadley::Middleware, token_store: token_store
|
44
|
+
|
45
|
+
5. Run <tt>rake middleware</tt> from your project root and verify that <tt>Warden::Manager</tt> appears after <tt>ActionDispatch::Session::CookieStore</tt> and <tt>Hadley::Middleware</tt> appears after <tt>Warden::Manager</tt>
|
data/config.ru
CHANGED
@@ -29,7 +29,7 @@ class ExampleResourceServer < Sinatra::Base
|
|
29
29
|
|
30
30
|
use Warden::Manager do |manager|
|
31
31
|
manager.basic(:server) do |basic|
|
32
|
-
basic.hash_credentials
|
32
|
+
basic.hash_credentials true
|
33
33
|
basic.lookup do |id, secret|
|
34
34
|
[ id, secret] == [
|
35
35
|
'a8ab10237100f16d12b6c8e574e84b92cc15aecaced04d47251a5f34ffaa0e60',
|
@@ -38,12 +38,12 @@ class ExampleResourceServer < Sinatra::Base
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
manager.bearer(:client) do |bearer|
|
41
|
-
bearer.token_store
|
42
|
-
bearer.anonymous_allowed
|
41
|
+
bearer.token_store token_store
|
42
|
+
bearer.anonymous_allowed true
|
43
43
|
end
|
44
44
|
manager.bearer(:user) do |bearer|
|
45
|
-
bearer.token_store
|
46
|
-
bearer.anonymous_allowed
|
45
|
+
bearer.token_store token_store
|
46
|
+
bearer.anonymous_allowed false
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
data/hadley.gemspec
CHANGED
@@ -17,6 +17,8 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = [ 'lib' ]
|
19
19
|
|
20
|
+
s.add_development_dependency 'yard'
|
21
|
+
s.add_development_dependency 'yard-sinatra'
|
20
22
|
s.add_development_dependency 'rspec'
|
21
23
|
s.add_development_dependency 'active_support'
|
22
24
|
s.add_development_dependency 'dalli'
|
data/lib/hadley.rb
CHANGED
@@ -2,6 +2,7 @@ autoload :Rack, 'rack'
|
|
2
2
|
autoload :Sinatra, 'sinatra/base'
|
3
3
|
autoload :Warden, 'warden'
|
4
4
|
|
5
|
+
# This module is a namespace for all modules and classes related to the AFID resource server rack middleware
|
5
6
|
module Hadley
|
6
7
|
|
7
8
|
autoload :Authz, 'hadley/authz'
|
@@ -10,8 +11,10 @@ module Hadley
|
|
10
11
|
autoload :TokenStore, 'hadley/token_store'
|
11
12
|
autoload :Utils, 'hadley/utils'
|
12
13
|
|
13
|
-
|
14
|
+
# The current version of this ruby gem
|
15
|
+
VERSION = '0.0.3'
|
14
16
|
|
17
|
+
# The identity key for the AFID anonymous identity
|
15
18
|
ANONYMOUS_IDENTITY = '0' * 66
|
16
19
|
|
17
20
|
end
|
data/lib/hadley/authz.rb
CHANGED
@@ -1,53 +1,23 @@
|
|
1
|
+
# This module is both a namespace for modules and classes related to AFID authorization and a collection of useful
|
2
|
+
# helper methods that can be mixed in to application controllers / route handlers that need to interact with warden and
|
3
|
+
# AFID authorization details.
|
1
4
|
module Hadley::Authz
|
2
5
|
|
6
|
+
autoload :StrategyBuilder, 'hadley/authz/strategy_builder'
|
7
|
+
autoload :Strategy, 'hadley/authz/strategy'
|
3
8
|
autoload :Basic, 'hadley/authz/basic'
|
4
9
|
autoload :Bearer, 'hadley/authz/bearer'
|
5
10
|
|
11
|
+
# A wrapper method that allows cleaner access to the warden proxy
|
12
|
+
#
|
13
|
+
# @return [Warden::Proxy] The warden lazy object equivalent to <tt>env['warden']</tt>.
|
6
14
|
def warden
|
7
15
|
env['warden']
|
8
16
|
end
|
9
17
|
|
10
|
-
|
11
|
-
|
12
|
-
def build(name, config)
|
13
|
-
strategy = self.create_strategy(name)
|
14
|
-
self.register_strategy(name, strategy)
|
15
|
-
self.set_config(strategy, config)
|
16
|
-
end
|
17
|
-
|
18
|
-
protected
|
19
|
-
|
20
|
-
def create_strategy(name)
|
21
|
-
class_name = Hadley::Utils.camelize(name.to_s)
|
22
|
-
if self.const_defined?(class_name)
|
23
|
-
self.const_get(class_name)
|
24
|
-
else
|
25
|
-
self.const_set(class_name, Class.new(self))
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def register_strategy(name, strategy)
|
30
|
-
full_name = "afid_#{name}".to_sym
|
31
|
-
if Warden::Strategies[full_name].nil?
|
32
|
-
Warden::Strategies.add(full_name, strategy)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def set_config(strategy, config)
|
37
|
-
strategy.const_set("CONFIG", config) unless strategy.const_defined?("CONFIG")
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
class Strategy < Warden::Strategies::Base
|
43
|
-
extend StrategyBuilder
|
44
|
-
|
45
|
-
def config
|
46
|
-
self.class.const_get("CONFIG")
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
18
|
+
# Add the warden config extension for the Basic authorization strategy
|
50
19
|
Warden::Config.send(:include, Hadley::Authz::Basic::ConfigExtension)
|
20
|
+
# Add the warden config extension for the Bearer authorization strategy
|
51
21
|
Warden::Config.send(:include, Hadley::Authz::Bearer::ConfigExtension)
|
52
22
|
|
53
23
|
end
|
data/lib/hadley/authz/basic.rb
CHANGED
@@ -1,56 +1,77 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
module Authz
|
1
|
+
# This module is a namespace for modules and classes related to the HTTP Basic authorization strategy.
|
2
|
+
module Hadley::Authz::Basic
|
4
3
|
|
5
|
-
|
4
|
+
# This class is the prototype class for all HTTP Basic authorization strategies used by hadley.
|
5
|
+
class Strategy < Hadley::Authz::Strategy
|
6
6
|
|
7
|
-
|
7
|
+
# Provides access to the HTTP Basic Auth information assiciated with the current request.
|
8
|
+
#
|
9
|
+
# @return [Rack::Aauth::Basic::Request] The HTTP Basic Auth information associated with the current request.
|
10
|
+
def auth
|
11
|
+
@auth ||= Rack::Auth::Basic::Request.new(env)
|
12
|
+
end
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
14
|
+
# Identifies whether a login using this strategy should be persisted across multiple requests.
|
15
|
+
#
|
16
|
+
# @see Warden::Strategies::Base#store?
|
17
|
+
#
|
18
|
+
# @return [Boolean] true if and only if a login using this strategy should be persistent across multiple requests.
|
19
|
+
def store?
|
20
|
+
false
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
23
|
+
# Authenticates the entity identified by the provided HTTP Basic Auth information
|
24
|
+
def authenticate!
|
25
|
+
return unauthorized unless auth.provided? and auth.basic? and auth.credentials
|
26
|
+
credentials = auth.credentials.map do |credential|
|
27
|
+
config.hash_credentials ? Digest::SHA2.new(256).update(credential).to_s : credential
|
28
|
+
end
|
29
|
+
user = config.lookup.call(credentials.first, credentials.last)
|
30
|
+
return user ? success!(auth.credentials.first) : unauthorized
|
31
|
+
end
|
16
32
|
|
17
|
-
|
18
|
-
return unauthorized unless auth.provided? and auth.basic? and auth.credentials
|
19
|
-
credentials = auth.credentials.map do |credential|
|
20
|
-
config.hash_credentials ? Digest::SHA2.new(256).update(credential).to_s : credential
|
21
|
-
end
|
22
|
-
user = config.lookup.call(credentials.first, credentials.last)
|
23
|
-
return user ? success!(auth.credentials.first) : unauthorized
|
24
|
-
end
|
33
|
+
private
|
25
34
|
|
26
|
-
|
27
|
-
|
28
|
-
|
35
|
+
# Renders a custom HTTP 401 Unauthorized response with the appropriate challenge.
|
36
|
+
def unauthorized
|
37
|
+
custom!(Rack::Response.new([config.fail_message], 401, { 'WWW-Authenticate' => %Q{Basic realm="#{config.realm}"} }))
|
38
|
+
end
|
29
39
|
|
30
|
-
|
40
|
+
end
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
# This module provides the configuration extension to Warden allowing for ease of configuration for basic auth
|
43
|
+
# strategies via the following syntax:
|
44
|
+
#
|
45
|
+
# use Warden::Manager do |manager|
|
46
|
+
# manager.basic(:server) do |basic|
|
47
|
+
# basic.hash_credentials true
|
48
|
+
# basic.lookup do |id, secret|
|
49
|
+
# [ id, secret] == [ 'client_identity', 'client_secret' ] ? id : nil
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
module ConfigExtension
|
54
|
+
|
55
|
+
# Configures and registers and new basic authorization strategy.
|
56
|
+
#
|
57
|
+
# @param [Symbol] name The unqualified name for the new basic authorization strategy.
|
58
|
+
# @param [Hadley::Config] config The configuration specific to the new basic authorization strategy.
|
59
|
+
def basic(name, &block)
|
60
|
+
config = Hadley::Config.new(
|
61
|
+
realm: 'Access Tokens',
|
62
|
+
fail_message: 'Authorization Failed',
|
63
|
+
hash_credentials: false
|
64
|
+
)
|
65
|
+
if block_given?
|
66
|
+
if block.arity == 1
|
67
|
+
yield config
|
68
|
+
else
|
69
|
+
config.instance_eval(&block)
|
48
70
|
end
|
49
|
-
|
50
71
|
end
|
51
|
-
|
72
|
+
Hadley::Authz::Basic::Strategy.build(name, config) unless config.lookup.nil?
|
52
73
|
end
|
53
|
-
|
74
|
+
|
54
75
|
end
|
55
76
|
|
56
77
|
end
|
data/lib/hadley/authz/bearer.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
|
+
# This module is a namespace for modules and classes related to bearer token based custom rack authorization requests
|
1
2
|
module Rack::Auth::Bearer
|
2
3
|
|
4
|
+
# This class represents a custom rack authorization request type for bearer token based authorization
|
3
5
|
class Request < Rack::Auth::AbstractRequest
|
4
6
|
|
7
|
+
# Provides a means to determin if the current requests authorization type is 'Bearer'.
|
8
|
+
#
|
9
|
+
# @return [Boolean] true if and only if the current requests authorization type is 'Bearer'.
|
5
10
|
def bearer?
|
6
11
|
:bearer == scheme
|
7
12
|
end
|
8
13
|
|
14
|
+
# Provides access to the bearer token associated with the current request.
|
15
|
+
#
|
16
|
+
# @return [String] The token assiciated with the current request.
|
9
17
|
def token
|
10
18
|
@token ||= params.split(' ', 2).first
|
11
19
|
end
|
@@ -14,59 +22,76 @@ module Rack::Auth::Bearer
|
|
14
22
|
|
15
23
|
end
|
16
24
|
|
17
|
-
module
|
18
|
-
|
19
|
-
module Authz
|
25
|
+
# This module is a namespace for modules and classes related to bearer token based authorization strategies.
|
26
|
+
module Hadley::Authz::Bearer
|
20
27
|
|
21
|
-
|
28
|
+
|
29
|
+
class Strategy < Hadley::Authz::Strategy
|
22
30
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
# Provides access to the bearer token based auth information assiciated with the current request.
|
32
|
+
#
|
33
|
+
# @return [Rack::Aauth::Bearer::Request] The bearer token based auth information assiciated with the current request.
|
34
|
+
def auth
|
35
|
+
@auth ||= Rack::Auth::Bearer::Request.new(env)
|
36
|
+
end
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
38
|
+
# Identifies whether a login using this strategy should be persisted across multiple requests.
|
39
|
+
#
|
40
|
+
# @see Warden::Strategies::Base#store?
|
41
|
+
#
|
42
|
+
# @return [Boolean] true if and only if a login using this strategy should be persistent across multiple requests.
|
43
|
+
def store?
|
44
|
+
false
|
45
|
+
end
|
32
46
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
47
|
+
# Authenticates the entity identified by the provided bearer token.
|
48
|
+
def authenticate!(anonymous_allowed=false)
|
49
|
+
return unauthorized unless auth.provided? and auth.bearer? and auth.token
|
50
|
+
user = config.token_store.get(auth.token)
|
51
|
+
return unauthorized unless user and (!user[:anonymous] or config.anonymous_allowed)
|
52
|
+
success!(user)
|
53
|
+
end
|
39
54
|
|
40
|
-
|
55
|
+
private
|
41
56
|
|
42
|
-
|
43
|
-
|
44
|
-
|
57
|
+
# Renders a custom HTTP 401 Unauthorized response with the appropriate challenge.
|
58
|
+
def unauthorized
|
59
|
+
custom!(Rack::Response.new([config.fail_message], 401, { 'WWW-Authenticate' => %Q{Bearer realm="#{config.realm}"} }))
|
60
|
+
end
|
45
61
|
|
46
|
-
|
62
|
+
end
|
47
63
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
+
# This module provides the configuration extension to Warden allowing for ease of configuration for bearer token
|
65
|
+
# based authorization strategies via the following syntax:
|
66
|
+
#
|
67
|
+
# use Warden::Manager do |manager|
|
68
|
+
# manager.bearer(:server) do |bearer|
|
69
|
+
# bearer.token_store token_store
|
70
|
+
# bearer.anonymous_allowed true
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
module ConfigExtension
|
74
|
+
|
75
|
+
# Configures and registers and new bearer token based authorization strategy.
|
76
|
+
#
|
77
|
+
# @param [Symbol] name The unqualified name for the new bearer token based authorization strategy.
|
78
|
+
# @param [Hadley::Config] config The configuration specific to the new bearer token based authorization strategy.
|
79
|
+
def bearer(name, &block)
|
80
|
+
config = Hadley::Config.new(
|
81
|
+
realm: 'Access Tokens',
|
82
|
+
fail_message: 'Authorization Failed',
|
83
|
+
anonymous_allowed: false
|
84
|
+
)
|
85
|
+
if block_given?
|
86
|
+
if block.arity == 1
|
87
|
+
yield config
|
88
|
+
else
|
89
|
+
config.instance_eval(&block)
|
64
90
|
end
|
65
|
-
|
66
91
|
end
|
67
|
-
|
92
|
+
Hadley::Authz::Bearer::Strategy.build(name, config) unless config.token_store.nil?
|
68
93
|
end
|
69
|
-
|
94
|
+
|
70
95
|
end
|
71
96
|
|
72
97
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# This class is a base class for authorization strategies
|
2
|
+
class Hadley::Authz::Strategy < Warden::Strategies::Base
|
3
|
+
extend Hadley::Authz::StrategyBuilder
|
4
|
+
|
5
|
+
# Provides access to the configuration for this authorization strategy.
|
6
|
+
#
|
7
|
+
# @return [Hadley::Config] the configuration for this authorization strategy.
|
8
|
+
def config
|
9
|
+
self.class.const_get("CONFIG")
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# This mixin module provides helpful utilties for generating new authorization strategies based on configuration
|
2
|
+
# provided when the strategy is being registered with Warden.
|
3
|
+
module Hadley::Authz::StrategyBuilder
|
4
|
+
|
5
|
+
# Builds a new authorization strategy class based on the provided configuration.
|
6
|
+
#
|
7
|
+
# @param [Symbol] name The unqualified name of the authorization strategy to be built.
|
8
|
+
# @param [Hadley::Config] config The configuration for the authorization strategy to be built.
|
9
|
+
def build(name, config)
|
10
|
+
strategy = self.create_strategy(name)
|
11
|
+
self.register_strategy(name, strategy)
|
12
|
+
self.set_config(strategy, config)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
# Creates the strategy class based on the provided name. The class will be namespaced under the class that
|
18
|
+
# these methods are mixed into.
|
19
|
+
#
|
20
|
+
# @param [Symbol] name The unqualified name of the authorization strategy to be built.
|
21
|
+
#
|
22
|
+
# @return [Class] The new strategy class.
|
23
|
+
def create_strategy(name)
|
24
|
+
class_name = Hadley::Utils.camelize(name.to_s)
|
25
|
+
if self.const_defined?(class_name)
|
26
|
+
self.const_get(class_name)
|
27
|
+
else
|
28
|
+
self.const_set(class_name, Class.new(self))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Registers the new authorization strategy with Warden under the specified name prefixed by 'afid_'.
|
33
|
+
#
|
34
|
+
# @param [Symbol] name The unqualified name of the authorization strategy to be built.
|
35
|
+
# @param [Class] strategy The newly created strategy class.
|
36
|
+
def register_strategy(name, strategy)
|
37
|
+
full_name = "afid_#{name}".to_sym
|
38
|
+
if Warden::Strategies[full_name].nil?
|
39
|
+
Warden::Strategies.add(full_name, strategy)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Binds the configuration information to the newly created strategy class.
|
44
|
+
#
|
45
|
+
# @param [Class] strategy The newly created strategy class.
|
46
|
+
# @param [Hadley::Config] config The configuration to be bound to the authorization strategy.
|
47
|
+
def set_config(strategy, config)
|
48
|
+
strategy.const_set("CONFIG", config) unless strategy.const_defined?("CONFIG")
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/lib/hadley/config.rb
CHANGED
@@ -1,28 +1,70 @@
|
|
1
|
+
# This class is a convenience wrapper around an Hash that provides a more expressive api for initial configuration and
|
2
|
+
# referencing the configuration information at runtime. For example:
|
3
|
+
# config.prop 'value' # --> config[:prop] = 'value'
|
4
|
+
# config.prop = 'value' # --> same as above
|
5
|
+
# config.props 'a', 'b', 'c' # --> config[:props] = [ 'a', 'b', 'c' ]
|
6
|
+
# config.props = 'a', 'b', 'c' # same as above
|
7
|
+
# config.callback { |it| puts it } # config[:callback] = { |it| puts it }
|
8
|
+
# config.prop # --> config[:prop]
|
1
9
|
class Hadley::Config
|
2
10
|
|
3
|
-
|
4
|
-
|
11
|
+
# Initializes this Config with the specified defaults.
|
12
|
+
#
|
13
|
+
# @param [Hash] defaults The default configuration values for this Config instance.
|
14
|
+
def initialize(defaults={})
|
15
|
+
@config = defaults
|
5
16
|
end
|
6
17
|
|
18
|
+
# Delegates to {#set}, {#get} or {#proc} depending on the nature of the given name, if a block is given or if the args
|
19
|
+
# array is not empty.
|
20
|
+
#
|
21
|
+
# @param [String] name The name of the property to be read or written. If the name ends with '=' it will be stripped
|
22
|
+
# from the name and the operation will be treated as a write.
|
23
|
+
# @param [*Object] args The optional array of property values to be assigned to the provided property name. If this
|
24
|
+
# array is not empty then {#set} will be called.
|
25
|
+
# @param [Proc] &block The optional block to be assigned to the provided property name. If the operation has a block
|
26
|
+
# given then {#proc} will be called.
|
27
|
+
#
|
28
|
+
# @return [Object,nil] The value ultimately written to or read from the given property name.
|
7
29
|
def method_missing(name, *args, &block)
|
8
30
|
if block_given?
|
9
31
|
proc(name, &block)
|
10
32
|
elsif name =~ /(.+)=$/
|
11
|
-
set($1, *args
|
33
|
+
set($1, *args)
|
34
|
+
elsif not args.empty?
|
35
|
+
set(name, *args)
|
12
36
|
else
|
13
|
-
get(name
|
37
|
+
get(name)
|
14
38
|
end
|
15
39
|
end
|
16
40
|
|
41
|
+
# Stores the given block under the provided property name.
|
42
|
+
#
|
43
|
+
# @param [String] name The name of the property to be written.
|
44
|
+
# @param [Proc] &block The block to be assigned to the provided property name.
|
45
|
+
#
|
46
|
+
# @return [Proc] The block written to the provided name.
|
17
47
|
def proc(name, &block)
|
18
48
|
@config[name.to_sym] = block
|
19
49
|
end
|
20
50
|
|
51
|
+
# Stores the value or values indicated by the args array under the provided property name.
|
52
|
+
#
|
53
|
+
# @param [String] name The name of the property to be written.
|
54
|
+
# @param [*Object] args The value or values to be assigned to the provided property name. If a single value is found
|
55
|
+
# a scalar will be written otherwise an array will be written.
|
56
|
+
#
|
57
|
+
# @return [Object,nil] The value written to the provided name.
|
21
58
|
def set(name, *args)
|
22
59
|
@config[name.to_sym] = args.size == 1 ? args.first : args
|
23
60
|
end
|
24
61
|
|
25
|
-
|
62
|
+
# Retrieves the value stored under the provided name.
|
63
|
+
#
|
64
|
+
# @param [String] name The name of the property to be read.
|
65
|
+
#
|
66
|
+
# @return [Object,nil] The value stored under the provided name or nil if no such value exists.
|
67
|
+
def get(name)
|
26
68
|
@config[name.to_sym]
|
27
69
|
end
|
28
70
|
|
data/lib/hadley/middleware.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
+
# This class provides the rack middleware that builds on top of warden to provide the necessary endpoints for a rack
|
2
|
+
# application to function as an AFID protected resource.
|
1
3
|
class Hadley::Middleware < Sinatra::Base
|
2
4
|
|
3
5
|
include Hadley::Authz
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
# Initializes the middleware with the provided application and options
|
8
|
+
#
|
9
|
+
# @param [Rack::Application] app The rack application that this middleware is participating in
|
10
|
+
# @param [Hash] options The Hash of keyword arguments
|
11
|
+
# @param [Hadley::TokenStore] options.store The token store to be used for persisting tokens provisioned by the AFID
|
12
|
+
# authorization server
|
7
13
|
def initialize(app=nil, options={})
|
8
14
|
super(app)
|
9
15
|
@config ||= Hadley::Config.new(options)
|
@@ -12,10 +18,7 @@ class Hadley::Middleware < Sinatra::Base
|
|
12
18
|
self
|
13
19
|
end
|
14
20
|
|
15
|
-
#
|
16
|
-
# Routes
|
17
|
-
# ------------------------------------------
|
18
|
-
|
21
|
+
# The required endpoint for provisioning AFID access tokens.
|
19
22
|
put '/access/tokens/:token' do |token|
|
20
23
|
warden.authenticate!(:afid_server)
|
21
24
|
begin
|
@@ -30,6 +33,7 @@ class Hadley::Middleware < Sinatra::Base
|
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
36
|
+
# The required endpoint for invalidating AFID access tokens.
|
33
37
|
delete '/access/tokens/:token' do |token|
|
34
38
|
warden.authenticate!(:afid_server)
|
35
39
|
begin
|
data/lib/hadley/token_store.rb
CHANGED
@@ -1,13 +1,23 @@
|
|
1
|
+
# This class handles the storage, retrieval and removal of OAuth 2 bearer tokens sent from the AFID authorization
|
2
|
+
# server. The TokenStore delegates most of the work to the delegate store, which must support the api set forth by
|
3
|
+
# ActiveSupport::Cache::Store.
|
1
4
|
class Hadley::TokenStore
|
2
5
|
|
6
|
+
# This method initializes the TokenStore with the delegate store.
|
7
|
+
#
|
8
|
+
# @param [ActiveSupport::Cache::Store] store The TokenStore instance will delegate the heavy lifting to the provided
|
9
|
+
# store.
|
3
10
|
def initialize(store)
|
4
11
|
@store = store
|
5
12
|
end
|
6
13
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
14
|
+
# This method retrieves the AFID identity information associated with the provided token. If no such identity is
|
15
|
+
# found the result will be nil.
|
16
|
+
#
|
17
|
+
# @param [String] token The unique token provisioned by the AFID resource server.
|
18
|
+
#
|
19
|
+
# @return [Hash, nil] A Hash representation of the identity associated with the provided token or nil if no such identity
|
20
|
+
# exists.
|
11
21
|
def get(token)
|
12
22
|
access = @store.read(key_for(token))
|
13
23
|
if access
|
@@ -16,12 +26,37 @@ class Hadley::TokenStore
|
|
16
26
|
access
|
17
27
|
end
|
18
28
|
|
29
|
+
# This method stores the provided AFID identity information under the given AFID token for the duration of time
|
30
|
+
# specified by the expires_in argument.
|
31
|
+
#
|
32
|
+
# @param [String] token The unique token provisioned by the AFID resource server.
|
33
|
+
# @param [Integer] expires_in The duration of time (in seconds) that the provided AFID identity information should be
|
34
|
+
# stored.
|
35
|
+
# @param [Hash] data The identity information to be assiciated with the given AFID token.
|
36
|
+
#
|
37
|
+
# @return [Boolean, nil] True if and only if the identity information was stored successfully.
|
19
38
|
def put(token, expires_in, data={})
|
20
39
|
@store.write(key_for(token), data, expires_in: expires_in)
|
21
40
|
end
|
22
41
|
|
42
|
+
# This method removes the AFID identity information associated with the provided token.
|
43
|
+
#
|
44
|
+
# @param [String] token The token provisioned by the AFID resource server.
|
45
|
+
#
|
46
|
+
# @return [Boolean, nil] True if an only if the identity information was removed successfully.
|
23
47
|
def delete(token)
|
24
48
|
@store.delete(key_for(token))
|
25
49
|
end
|
26
50
|
|
51
|
+
protected
|
52
|
+
|
53
|
+
# This method derives the appropriate datastore key from the given AFID token.
|
54
|
+
#
|
55
|
+
# @param [String] token The unique token provisioned by the AFID resource server.
|
56
|
+
#
|
57
|
+
# @return [Symbol] The appropriate datastore key for the given AFID token.
|
58
|
+
def key_for(token)
|
59
|
+
"afid-access-token:#{token}".to_sym
|
60
|
+
end
|
61
|
+
|
27
62
|
end
|
data/lib/hadley/utils.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
+
# This module contains a collection of generally useful methods that (currently) have no better place to live. They can
|
2
|
+
# either be referenced directly as module methods or be mixed in.
|
1
3
|
module Hadley::Utils
|
2
4
|
extend self
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
+
# This method will derive a camelized name from the provided underscored name.
|
7
|
+
#
|
8
|
+
# @param [#to_s] name The underscored name to be camelized.
|
9
|
+
# @param [Boolean] uc_first True if and only if the first letter of the resulting camelized name should be
|
10
|
+
# capitalized.
|
11
|
+
#
|
12
|
+
# @return [String] The camelized name corresponding to the provided underscored name.
|
13
|
+
def camelize(name, uc_first=true)
|
14
|
+
parts = name.to_s.split('_')
|
6
15
|
assemble = lambda { |head, tail| head + tail.capitalize }
|
7
16
|
uc_first ? parts.inject('', &assemble) : parts.inject(&assemble)
|
8
17
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hadley
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,33 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: yard
|
16
|
+
requirement: &70230747821600 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70230747821600
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard-sinatra
|
27
|
+
requirement: &70230747821000 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70230747821000
|
14
36
|
- !ruby/object:Gem::Dependency
|
15
37
|
name: rspec
|
16
|
-
requirement: &
|
38
|
+
requirement: &70230747820200 !ruby/object:Gem::Requirement
|
17
39
|
none: false
|
18
40
|
requirements:
|
19
41
|
- - ! '>='
|
@@ -21,10 +43,10 @@ dependencies:
|
|
21
43
|
version: '0'
|
22
44
|
type: :development
|
23
45
|
prerelease: false
|
24
|
-
version_requirements: *
|
46
|
+
version_requirements: *70230747820200
|
25
47
|
- !ruby/object:Gem::Dependency
|
26
48
|
name: active_support
|
27
|
-
requirement: &
|
49
|
+
requirement: &70230747819480 !ruby/object:Gem::Requirement
|
28
50
|
none: false
|
29
51
|
requirements:
|
30
52
|
- - ! '>='
|
@@ -32,10 +54,10 @@ dependencies:
|
|
32
54
|
version: '0'
|
33
55
|
type: :development
|
34
56
|
prerelease: false
|
35
|
-
version_requirements: *
|
57
|
+
version_requirements: *70230747819480
|
36
58
|
- !ruby/object:Gem::Dependency
|
37
59
|
name: dalli
|
38
|
-
requirement: &
|
60
|
+
requirement: &70230747818780 !ruby/object:Gem::Requirement
|
39
61
|
none: false
|
40
62
|
requirements:
|
41
63
|
- - ! '>='
|
@@ -43,10 +65,10 @@ dependencies:
|
|
43
65
|
version: '0'
|
44
66
|
type: :development
|
45
67
|
prerelease: false
|
46
|
-
version_requirements: *
|
68
|
+
version_requirements: *70230747818780
|
47
69
|
- !ruby/object:Gem::Dependency
|
48
70
|
name: sinatra
|
49
|
-
requirement: &
|
71
|
+
requirement: &70230747833560 !ruby/object:Gem::Requirement
|
50
72
|
none: false
|
51
73
|
requirements:
|
52
74
|
- - ! '>='
|
@@ -54,10 +76,10 @@ dependencies:
|
|
54
76
|
version: '0'
|
55
77
|
type: :runtime
|
56
78
|
prerelease: false
|
57
|
-
version_requirements: *
|
79
|
+
version_requirements: *70230747833560
|
58
80
|
- !ruby/object:Gem::Dependency
|
59
81
|
name: warden
|
60
|
-
requirement: &
|
82
|
+
requirement: &70230747832820 !ruby/object:Gem::Requirement
|
61
83
|
none: false
|
62
84
|
requirements:
|
63
85
|
- - ! '>='
|
@@ -65,7 +87,7 @@ dependencies:
|
|
65
87
|
version: '0'
|
66
88
|
type: :runtime
|
67
89
|
prerelease: false
|
68
|
-
version_requirements: *
|
90
|
+
version_requirements: *70230747832820
|
69
91
|
description:
|
70
92
|
email:
|
71
93
|
- bitbutcher@gmail.com
|
@@ -74,7 +96,10 @@ extensions: []
|
|
74
96
|
extra_rdoc_files: []
|
75
97
|
files:
|
76
98
|
- .gitignore
|
99
|
+
- .yardopts
|
77
100
|
- Gemfile
|
101
|
+
- LICENSE.txt
|
102
|
+
- README.rdoc
|
78
103
|
- Rakefile
|
79
104
|
- config.ru
|
80
105
|
- hadley.gemspec
|
@@ -82,6 +107,8 @@ files:
|
|
82
107
|
- lib/hadley/authz.rb
|
83
108
|
- lib/hadley/authz/basic.rb
|
84
109
|
- lib/hadley/authz/bearer.rb
|
110
|
+
- lib/hadley/authz/strategy.rb
|
111
|
+
- lib/hadley/authz/strategy_builder.rb
|
85
112
|
- lib/hadley/config.rb
|
86
113
|
- lib/hadley/middleware.rb
|
87
114
|
- lib/hadley/token_store.rb
|
@@ -111,3 +138,4 @@ signing_key:
|
|
111
138
|
specification_version: 3
|
112
139
|
summary: Rack middleware for AFID(bby-id) resource server implementations
|
113
140
|
test_files: []
|
141
|
+
has_rdoc:
|