warden-oauth2-strategies 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +13 -0
- data/Guardfile +11 -0
- data/README.md +196 -0
- data/Rakefile +8 -0
- data/lib/warden/oauth2/error_app.rb +23 -0
- data/lib/warden/oauth2/failure_app.rb +22 -0
- data/lib/warden/oauth2/strategies/base.rb +17 -0
- data/lib/warden/oauth2/strategies/bearer.rb +30 -0
- data/lib/warden/oauth2/strategies/client.rb +57 -0
- data/lib/warden/oauth2/strategies/client_credentials.rb +16 -0
- data/lib/warden/oauth2/strategies/public.rb +21 -0
- data/lib/warden/oauth2/strategies/resource_owner_password_credentials.rb +28 -0
- data/lib/warden/oauth2/strategies/token.rb +40 -0
- data/lib/warden/oauth2/version.rb +5 -0
- data/lib/warden/oauth2.rb +35 -0
- data/lib/warden-oauth2.rb +1 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/warden/oauth2/failure_app_spec.rb +30 -0
- data/spec/warden/oauth2/strategies/bearer_spec.rb +32 -0
- data/spec/warden/oauth2/strategies/client_credentials_spec.rb +28 -0
- data/spec/warden/oauth2/strategies/client_spec.rb +73 -0
- data/spec/warden/oauth2/strategies/public_spec.rb +26 -0
- data/spec/warden/oauth2/strategies/resource_owner_password_credentials_spec.rb +64 -0
- data/spec/warden/oauth2/strategies/token_spec.rb +56 -0
- data/warden-oauth2.gemspec +22 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9e885487f2c3e237d8a7cffa9020ea86671d015e
|
4
|
+
data.tar.gz: 9b6d3b51466a0861cf876fa606d407fd9fd2da18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f3fd02b16da09b9454a707f3d8843e969d907239f824caa136892c80734e63ac37b20befd927c4b5b6985fa87ca0dfcaca25228bd5c8672c8d57c3bbe743b69d
|
7
|
+
data.tar.gz: 4a1ddb4c062dec57fbb06ea3e3a7d8da582baba7063d267bef97d2e404f0fa35b07d1ff07955d73a612bc21065e9fb55d46503c0c95d92ee8a5a8475eaa538f5
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
# Warden::OAuth2 [![CI Status](https://secure.travis-ci.org/opperator/warden-oauth2.png)](http://travis-ci.org/opperator/warden-oauth2)
|
2
|
+
|
3
|
+
This is a fork of [original project](https://github.com/opperator/warden-oauth2) which is actually maintained.
|
4
|
+
This library provides a robust set of authorization strategies for
|
5
|
+
Warden meant to be used to implement an OAuth 2.0 (targeting RFC6749)
|
6
|
+
provider.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
### Grape API Example
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require 'grape'
|
14
|
+
require 'warden-oauth2'
|
15
|
+
|
16
|
+
class MyAPI < Grape::API
|
17
|
+
use Warden::Manager do |config|
|
18
|
+
strategies.add :bearer, Warden::OAuth2::Strategies::Bearer
|
19
|
+
strategies.add :client_credentials, Warden::OAuth2::Strategies::ClientCredentials
|
20
|
+
strategies.add :resource_owner_password_credentials, Warden::OAuth2::Strategies::ResourceOwnerPasswordCredentials
|
21
|
+
strategies.add :public, Warden::OAuth2::Strategies::Public
|
22
|
+
|
23
|
+
config.default_strategies :bearer, :client_credentials, :resource_owner_password_credentials, :public
|
24
|
+
config.failure_app Warden::OAuth2::FailureApp
|
25
|
+
end
|
26
|
+
|
27
|
+
helpers do
|
28
|
+
def warden; env['warden'] end
|
29
|
+
end
|
30
|
+
|
31
|
+
resources :hamburgers do
|
32
|
+
before do
|
33
|
+
warden.authenticate! scope: :hamburgers
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
## Configuration
|
40
|
+
|
41
|
+
You can configure Warden::OAuth2 like so:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Warden::OAuth2.configure do |config|
|
45
|
+
config.some_option = some_value
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### Configurable Options
|
50
|
+
|
51
|
+
* **client_credentials_model:** A client application class used for client credentials authentication. See **Models** below.
|
52
|
+
Defaults to `ClientCredentialsApplication`.
|
53
|
+
* **resource_owner_password_credentials_model:** A client application class used for resource owner password authentication. See **Models** below.
|
54
|
+
Defaults to `ResourceOwnerPasswordCredentialsApplication`.
|
55
|
+
* **token_model:** An access token class. See **Models** below. Defaults
|
56
|
+
to `AccessToken`.
|
57
|
+
|
58
|
+
## Models
|
59
|
+
|
60
|
+
You will need to supply data models to back up the persistent facets of
|
61
|
+
your OAuth 2.0 implementation. Below are examples of the interfaces that
|
62
|
+
each require.
|
63
|
+
|
64
|
+
### Client Credentials Application
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class ClientCredentialsApplication
|
68
|
+
# REQUIRED
|
69
|
+
def self.locate(client_id, client_secret = nil)
|
70
|
+
# Should return a client application matching the client_id
|
71
|
+
# provided, but should ONLY match client_secret if it is
|
72
|
+
# provided.
|
73
|
+
end
|
74
|
+
|
75
|
+
# OPTIONAL
|
76
|
+
def scope?(scope)
|
77
|
+
# True if the client should be able to access the scope passed
|
78
|
+
# (usually a symbol) without having an access token.
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
### Resource Owner Password Credentials Application
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
class ResourceOwnerPasswordCredentialsApplication
|
87
|
+
# REQUIRED
|
88
|
+
def self.locate(client_id, client_secret = nil)
|
89
|
+
# Should return a client application matching the client_id
|
90
|
+
# provided, but should ONLY match client_secret if it is
|
91
|
+
# provided.
|
92
|
+
end
|
93
|
+
#REQUIRED
|
94
|
+
def valid?(options={})
|
95
|
+
# Use options[:username] and options[:password] to check
|
96
|
+
# that specified credentials are valid
|
97
|
+
end
|
98
|
+
|
99
|
+
# OPTIONAL
|
100
|
+
def scope?(scope)
|
101
|
+
# True if the client should be able to access the scope passed
|
102
|
+
# (usually a symbol) without having an access token.
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
### Access Token
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class AccessToken
|
111
|
+
# REQUIRED
|
112
|
+
def self.locate(token_string)
|
113
|
+
# Should return an access token matching the string provided.
|
114
|
+
# Note that you MAY include expired access tokens in the result
|
115
|
+
# of this method so long as you implement an instance #expired?
|
116
|
+
# method.
|
117
|
+
end
|
118
|
+
|
119
|
+
# OPTIONAL
|
120
|
+
def expired?
|
121
|
+
# True if the access token has reached its expiration.
|
122
|
+
end
|
123
|
+
|
124
|
+
# OPTIONAL
|
125
|
+
def scope?(scope)
|
126
|
+
# True if the scope passed in (usually a symbol) has been authorized
|
127
|
+
# for this access token.
|
128
|
+
end
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
## Strategies
|
133
|
+
|
134
|
+
### Bearer
|
135
|
+
|
136
|
+
This strategy authenticates by trying to find an access token that is
|
137
|
+
supplied according to the OAuth 2.0 Bearer Token specification
|
138
|
+
([draft 8][oauth2-bearer]). It does this by first extracting the access
|
139
|
+
token in string form and then calling the `.locate` method on your
|
140
|
+
access token model (see **Configuration** above).
|
141
|
+
|
142
|
+
Token-based strategies will also fail if they are expired or lack
|
143
|
+
sufficient scope. See **Models** above.
|
144
|
+
|
145
|
+
**User:** The Warden user is set to the client application returned by
|
146
|
+
`.locate`.
|
147
|
+
|
148
|
+
### Client credentials
|
149
|
+
|
150
|
+
This strategy authenticates an OAuth 2.0 client application directly for
|
151
|
+
endpoints that don't require a specific user. You might use this
|
152
|
+
strategy when you want to create an API for client statistics or if you
|
153
|
+
wish to rate limit based on a client application even for publicly
|
154
|
+
accessible endpoints.
|
155
|
+
|
156
|
+
**User:** The Warden user is set to the access token returned by `.locate`.
|
157
|
+
|
158
|
+
### Resource Owner Password Credential
|
159
|
+
|
160
|
+
This strategy creates an access token for a user with matching credentials.
|
161
|
+
Use `.valid?` on the client application to determine if user credentials are correct.
|
162
|
+
|
163
|
+
**User:** The Warden user is set to the access token returned by `.locate`.
|
164
|
+
|
165
|
+
### Public
|
166
|
+
|
167
|
+
This strategy succeeds by default and only fails if the authentication
|
168
|
+
scope is set and is something other than `:public`.
|
169
|
+
|
170
|
+
**User:** The Warden user is set to `nil`.
|
171
|
+
|
172
|
+
[oauth2]: http://tools.ietf.org/html/draft-ietf-oauth-v2-22
|
173
|
+
[oauth2-bearer]: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08
|
174
|
+
|
175
|
+
## License
|
176
|
+
The MIT License
|
177
|
+
|
178
|
+
Copyright (c) 2014 AirService Pty Ltd. http://www.airservice.com
|
179
|
+
|
180
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
181
|
+
of this software and associated documentation files (the "Software"), to deal
|
182
|
+
in the Software without restriction, including without limitation the rights
|
183
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
184
|
+
copies of the Software, and to permit persons to whom the Software is
|
185
|
+
furnished to do so, subject to the following conditions:
|
186
|
+
|
187
|
+
The above copyright notice and this permission notice shall be included in
|
188
|
+
all copies or substantial portions of the Software.
|
189
|
+
|
190
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
191
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
192
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
193
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
194
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
195
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
196
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'warden-oauth2'
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module OAuth2
|
5
|
+
class ErrorApp
|
6
|
+
def self.call(env)
|
7
|
+
new.call(env)
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
warden = env['warden']
|
12
|
+
strategy = warden.winning_strategy
|
13
|
+
status = strategy.respond_to?(:error_status) ? strategy.error_status : 401
|
14
|
+
error_description = strategy.respond_to?(:error_description) ? strategy.error_description : ''
|
15
|
+
headers = {'Content-Type' => 'application/json'}
|
16
|
+
headers['X-Accepted-OAuth-Scopes'] = (strategy.scope || :public).to_s
|
17
|
+
body = %Q{"error":"#{strategy.message}", "error_description":"#{error_description}"}
|
18
|
+
|
19
|
+
Rack::Response.new(body, status, headers).finish
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Warden
|
2
|
+
module OAuth2
|
3
|
+
class FailureApp
|
4
|
+
def self.call(env)
|
5
|
+
new.call(env)
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
warden = env['warden']
|
10
|
+
strategy = warden.winning_strategy
|
11
|
+
|
12
|
+
body = '{"error":"' + strategy.message.to_s + '"}'
|
13
|
+
status = strategy.error_status rescue 401
|
14
|
+
headers = {'Content-Type' => 'application/json'}
|
15
|
+
|
16
|
+
headers['X-Accepted-OAuth-Scopes'] = (strategy.scope || :public).to_s
|
17
|
+
|
18
|
+
[status, headers, [body]]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'warden-oauth2'
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module OAuth2
|
5
|
+
module Strategies
|
6
|
+
class Bearer < Token
|
7
|
+
def valid?
|
8
|
+
!!token_string
|
9
|
+
end
|
10
|
+
|
11
|
+
def token_string
|
12
|
+
token_string_from_header || token_string_from_request_params
|
13
|
+
end
|
14
|
+
|
15
|
+
def token_string_from_header
|
16
|
+
Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS.each do |key|
|
17
|
+
if env.key?(key) && token_string = env[key][/^Bearer (.*)/, 1]
|
18
|
+
return token_string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def token_string_from_request_params
|
25
|
+
params[:access_token]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'warden-oauth2'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Warden
|
5
|
+
module OAuth2
|
6
|
+
module Strategies
|
7
|
+
class Client < Base
|
8
|
+
attr_reader :client, :client_id, :client_secret, :error_description
|
9
|
+
|
10
|
+
def authenticate!
|
11
|
+
@client = client_from_http_basic || client_from_request_params
|
12
|
+
|
13
|
+
if self.client
|
14
|
+
fail "invalid_scope" and return if scope && client.respond_to?(:scope) && !client.scope?(scope)
|
15
|
+
client_authenticated
|
16
|
+
else
|
17
|
+
fail "invalid_client"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def client_from_http_basic
|
22
|
+
return nil unless (env.keys & Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS).any?
|
23
|
+
@client_id, @client_secret = *Rack::Auth::Basic::Request.new(env).credentials
|
24
|
+
model.locate(self.client_id, self.client_secret)
|
25
|
+
end
|
26
|
+
|
27
|
+
def client_from_request_params
|
28
|
+
@client_id, @client_secret = params['client_id'], params['client_secret']
|
29
|
+
return nil unless self.client_id
|
30
|
+
model.locate(@client_id, @client_secret)
|
31
|
+
end
|
32
|
+
|
33
|
+
def public_client?
|
34
|
+
client && !client_secret
|
35
|
+
end
|
36
|
+
|
37
|
+
def error_status
|
38
|
+
case message
|
39
|
+
when "invalid_client" then 401
|
40
|
+
when "invalid_scope" then 403
|
41
|
+
else 400
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
attr_writer :error_description
|
47
|
+
def model
|
48
|
+
raise 'Model should be defined in a child strategy'
|
49
|
+
end
|
50
|
+
|
51
|
+
def client_authenticated
|
52
|
+
success! self.client
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'warden-oauth2'
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module OAuth2
|
5
|
+
module Strategies
|
6
|
+
class ClientCredentials < Client
|
7
|
+
def model
|
8
|
+
Warden::OAuth2.config.client_credentials_model
|
9
|
+
end
|
10
|
+
def valid?
|
11
|
+
params['grant_type'] == 'client_credentials'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'warden-oauth2'
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module OAuth2
|
5
|
+
module Strategies
|
6
|
+
class Public < Base
|
7
|
+
def authenticate!
|
8
|
+
if scope && scope.to_sym != :public
|
9
|
+
fail! "invalid_scope" and return
|
10
|
+
end
|
11
|
+
|
12
|
+
success! nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_status
|
16
|
+
401
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'warden-oauth2'
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module OAuth2
|
5
|
+
module Strategies
|
6
|
+
class ResourceOwnerPasswordCredentials< Client
|
7
|
+
def valid?
|
8
|
+
params['grant_type'] == 'password'
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def model
|
13
|
+
Warden::OAuth2.config.resource_owner_password_credentials_model
|
14
|
+
end
|
15
|
+
|
16
|
+
def client_authenticated
|
17
|
+
if params['username'] && params['password']
|
18
|
+
valid_client = client.valid?(username: params['username'], password: params['password'])
|
19
|
+
valid_client ? super : fail("invalid_client")
|
20
|
+
else
|
21
|
+
fail "invalid_request"
|
22
|
+
self.error_description = "username or password are not provided"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'warden-oauth2'
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
module OAuth2
|
5
|
+
module Strategies
|
6
|
+
class Token < Base
|
7
|
+
def valid?
|
8
|
+
!!token_string
|
9
|
+
end
|
10
|
+
|
11
|
+
def authenticate!
|
12
|
+
if token
|
13
|
+
fail! "invalid_token" and return if token.respond_to?(:expired?) && token.expired?
|
14
|
+
fail! "invalid_scope" and return if scope && token.respond_to?(:scope?) && !token.scope?(scope)
|
15
|
+
success! token
|
16
|
+
else
|
17
|
+
fail! "invalid_request" and return unless token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def token
|
22
|
+
Warden::OAuth2.config.token_model.locate(token_string)
|
23
|
+
end
|
24
|
+
|
25
|
+
def token_string
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def error_status
|
30
|
+
case message
|
31
|
+
when "invalid_token" then 401
|
32
|
+
when "invalid_scope" then 403
|
33
|
+
when "invalid_request" then 400
|
34
|
+
else 400
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'warden'
|
2
|
+
require 'warden/oauth2/version'
|
3
|
+
|
4
|
+
module Warden
|
5
|
+
module OAuth2
|
6
|
+
class Configuration
|
7
|
+
attr_accessor :client_credentials_model, :resource_owner_password_credentials_model, :token_model
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
self.client_credentials_model = ClientCredentialsApplication if defined?(ClientCredentialsApplication)
|
11
|
+
self.resource_owner_password_credentials_model = ResourceOwnerPasswordCredentialsApplication if defined?(ResourceOwnerPasswordCredentialsApplication)
|
12
|
+
self.token_model = AccessToken if defined?(AccessToken)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.config
|
17
|
+
@@config ||= Configuration.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.configure
|
21
|
+
yield config
|
22
|
+
end
|
23
|
+
|
24
|
+
autoload :FailureApp, 'warden/oauth2/failure_app'
|
25
|
+
module Strategies
|
26
|
+
autoload :Base, 'warden/oauth2/strategies/base'
|
27
|
+
autoload :Public, 'warden/oauth2/strategies/public'
|
28
|
+
autoload :Token, 'warden/oauth2/strategies/token'
|
29
|
+
autoload :Client, 'warden/oauth2/strategies/client'
|
30
|
+
autoload :ClientCredentials, 'warden/oauth2/strategies/client_credentials'
|
31
|
+
autoload :ResourceOwnerPasswordCredentials, 'warden/oauth2/strategies/resource_owner_password_credentials'
|
32
|
+
autoload :Bearer, 'warden/oauth2/strategies/bearer'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'warden/oauth2'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Warden::OAuth2::FailureApp do
|
4
|
+
let(:app){ subject }
|
5
|
+
let(:warden){ double(:winning_strategy => @strategy || strategy) }
|
6
|
+
let(:strategy){ double(:message => 'invalid_request') }
|
7
|
+
|
8
|
+
context 'with all info' do
|
9
|
+
before do
|
10
|
+
@strategy = double(:error_status => 502, :message => 'custom', :scope => 'random')
|
11
|
+
get '/unauthenticated', {}, 'warden' => warden
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should set the status from error_status if there is one' do
|
15
|
+
last_response.status.should == 502
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should set the message from the message' do
|
19
|
+
last_response.body.should == '{"error":"custom"}'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should set the content type' do
|
23
|
+
last_response.headers['Content-Type'].should == 'application/json'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should set the X-OAuth-Accepted-Scopes header' do
|
27
|
+
last_response.headers['X-Accepted-OAuth-Scopes'].should == 'random'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Warden::OAuth2::Strategies::Bearer do
|
4
|
+
let(:strategy){ Warden::OAuth2::Strategies::Bearer }
|
5
|
+
let(:token_model){ double(:AccessToken) }
|
6
|
+
subject{ strategy.new({'rack.input' => {}}) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Warden::OAuth2.config.token_model = token_model
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#token_string_from_header' do
|
13
|
+
Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS.each do |key|
|
14
|
+
it "should recognize a bearer token in the #{key} environment key" do
|
15
|
+
subject.stub(:env).and_return({key => "Bearer abc"})
|
16
|
+
subject.token_string_from_header.should == 'abc'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should ignore a non-bearer authorization header' do
|
21
|
+
subject.stub(:env).and_return('HTTP_AUTHORIZATION' => 'Other do do do')
|
22
|
+
subject.token_string_from_header.should be_nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#token_string_from_request_params' do
|
27
|
+
it 'should pull the :access_token param' do
|
28
|
+
subject.stub(:params).and_return(:access_token => 'abc')
|
29
|
+
subject.token_string_from_request_params.should == 'abc'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Warden::OAuth2::Strategies::ClientCredentials do
|
4
|
+
let(:strategy){ described_class }
|
5
|
+
let(:client_credentials_model){ double(:ClientApplication) }
|
6
|
+
subject{ strategy.new({'rack.input' => {}}) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Warden::OAuth2.config.client_credentials_model = client_credentials_model
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#valid?' do
|
13
|
+
it 'returns false if the grant type is not specified' do
|
14
|
+
subject.stub(:params).and_return({})
|
15
|
+
subject.should_not be_valid
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns true if the grant type is client_credentials' do
|
19
|
+
subject.stub(:params).and_return({'grant_type' => 'client_credentials'})
|
20
|
+
subject.should be_valid
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns false if the grant type is not client_credentials' do
|
24
|
+
subject.stub(:params).and_return({'grant_type' => 'whatever'})
|
25
|
+
subject.should_not be_valid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Warden::OAuth2::Strategies::Client do
|
4
|
+
let(:strategy){ Warden::OAuth2::Strategies::Client }
|
5
|
+
let(:client_model){ double(:ClientApplication) }
|
6
|
+
subject{ strategy.new({'rack.input' => {}}) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
subject.stub(:model).and_return(client_model)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#client_from_http_basic' do
|
13
|
+
it 'should call through to the client application class locate method' do
|
14
|
+
subject.stub(:env).and_return({
|
15
|
+
'HTTP_AUTHORIZATION' => "Basic #{Base64.encode64('id:secret')}"
|
16
|
+
})
|
17
|
+
|
18
|
+
client_model.should_receive(:locate).with('id','secret').and_return("booya")
|
19
|
+
subject.client_from_http_basic.should == "booya"
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should return nil if no HTTP Basic credentials are provided' do
|
23
|
+
subject.client_from_http_basic.should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#client_from_request_params' do
|
28
|
+
it 'should be nil if no client_id is provided' do
|
29
|
+
subject.stub(:params).and_return({'client_secret' => 'abc'})
|
30
|
+
subject.client_from_request_params.should be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should call through to locate if a client_id is present' do
|
34
|
+
subject.stub(:params).and_return({'client_id' => 'abc'})
|
35
|
+
client_model.should_receive(:locate).with('abc',nil)
|
36
|
+
subject.client_from_request_params
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should call through to locate if a client_id and secret are present' do
|
40
|
+
subject.stub(:params).and_return({'client_id' => 'abc', 'client_secret' => 'def'})
|
41
|
+
client_model.should_receive(:locate).with('abc','def')
|
42
|
+
subject.client_from_request_params
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#authorize!' do
|
47
|
+
it 'should succeed if a client is around' do
|
48
|
+
client_instance = double
|
49
|
+
client_model.stub(:locate).and_return(client_instance)
|
50
|
+
subject.stub(:params).and_return('client_id' => 'awesome')
|
51
|
+
subject._run!
|
52
|
+
subject.user.should == client_instance
|
53
|
+
subject.result.should == :success
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should fail if no credentials are passed' do
|
57
|
+
subject._run!
|
58
|
+
subject.result.should == :failure
|
59
|
+
subject.message.should == "invalid_client"
|
60
|
+
subject.error_status.should == 401
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should fail if insufficient scope is provided' do
|
64
|
+
client_model.stub(:locate).and_return(double(:respond_to? => true, :scope? => false))
|
65
|
+
subject.stub(:params).and_return('client_id' => 'abc')
|
66
|
+
subject.stub(:scope).and_return(:confidential_client)
|
67
|
+
subject._run!
|
68
|
+
subject.result.should == :failure
|
69
|
+
subject.message.should == "invalid_scope"
|
70
|
+
subject.error_status.should == 403
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Warden::OAuth2::Strategies::Public do
|
4
|
+
let(:env){ {'PATH_INFO' => '/resource'} }
|
5
|
+
let(:strategy){ Warden::OAuth2::Strategies::Public }
|
6
|
+
subject{ strategy.new(env) }
|
7
|
+
|
8
|
+
it 'should succeed with no scope' do
|
9
|
+
subject._run!
|
10
|
+
subject.result.should == :success
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should succeed with a :public scope' do
|
14
|
+
subject.stub(:scope).and_return(:public)
|
15
|
+
subject._run!
|
16
|
+
subject.result.should == :success
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should fail and halt with another scope' do
|
20
|
+
subject.stub(:scope).and_return(:user)
|
21
|
+
subject._run!
|
22
|
+
subject.should be_halted
|
23
|
+
subject.message.should == "invalid_scope"
|
24
|
+
subject.result.should == :failure
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Warden::OAuth2::Strategies::ResourceOwnerPasswordCredentials do
|
4
|
+
let(:strategy){ described_class }
|
5
|
+
let(:client_model){ double(:ClientApplication) }
|
6
|
+
subject{ strategy.new({'rack.input' => {}}) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Warden::OAuth2.config.resource_owner_password_credentials_model = client_model
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#valid?' do
|
13
|
+
it 'returns false if the grant type is not specified' do
|
14
|
+
subject.stub(:params).and_return({})
|
15
|
+
subject.should_not be_valid
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns true if the grant type is password' do
|
19
|
+
subject.stub(:params).and_return({'grant_type' => 'password'})
|
20
|
+
subject.should be_valid
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns false if the grant type is not password' do
|
24
|
+
subject.stub(:params).and_return({'grant_type' => 'whatever'})
|
25
|
+
subject.should_not be_valid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#authorize!' do
|
30
|
+
it 'should fail if a client is around but not valid' do
|
31
|
+
client_instance = double(:client_instance, valid?: false)
|
32
|
+
client_model.stub(locate: client_instance)
|
33
|
+
subject.stub(:params).and_return('client_id' => 'awesome', 'username' => 'someuser', 'password' => 'incorrect')
|
34
|
+
subject._run!
|
35
|
+
subject.message.should == "invalid_client"
|
36
|
+
subject.error_status.should == 401
|
37
|
+
end
|
38
|
+
it 'should fail if username and password are not provided' do
|
39
|
+
client_model.stub(locate: double)
|
40
|
+
subject.stub(:params).and_return('client_id' => 'awesome')
|
41
|
+
subject._run!
|
42
|
+
subject.message.should == "invalid_request"
|
43
|
+
subject.error_status.should == 400
|
44
|
+
subject.error_description.should_not be_empty
|
45
|
+
end
|
46
|
+
it 'should pass username and password to validation check' do
|
47
|
+
client_instance = double(:client_instance)
|
48
|
+
client_model.stub(locate: client_instance)
|
49
|
+
subject.stub(:params).and_return('client_id' => 'awesome', 'username' => 'username', 'password' => 'password')
|
50
|
+
|
51
|
+
client_instance.should_receive(:valid?).with(username: 'username', password: 'password').and_return(false)
|
52
|
+
|
53
|
+
subject._run!
|
54
|
+
end
|
55
|
+
it 'should succeed if a client is around and valid' do
|
56
|
+
client_instance = double(:client_instance, valid?: true)
|
57
|
+
client_model.stub(locate: client_instance)
|
58
|
+
subject.stub(:params).and_return('client_id' => 'awesome', 'username' => 'username', 'password' => 'correct')
|
59
|
+
subject._run!
|
60
|
+
subject.user.should == client_instance
|
61
|
+
subject.result.should == :success
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Warden::OAuth2::Strategies::Token do
|
4
|
+
let(:token_model){ double }
|
5
|
+
let(:strategy){ Warden::OAuth2::Strategies::Token }
|
6
|
+
subject{ strategy.new({'rack.input' => {}}) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Warden::OAuth2.config.token_model = token_model
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#token' do
|
13
|
+
it 'should call through to .locate on the token_class with the token string' do
|
14
|
+
token_model.should_receive(:locate).with('abc')
|
15
|
+
subject.stub(:token_string).and_return('abc')
|
16
|
+
subject.token
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#authenticate!' do
|
21
|
+
it 'should be successful if there is a token' do
|
22
|
+
token_instance = double
|
23
|
+
subject.stub(:token).and_return(token_instance)
|
24
|
+
subject._run!
|
25
|
+
subject.result.should == :success
|
26
|
+
subject.user.should == token_instance
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should fail if there is not a token' do
|
30
|
+
subject.stub(token: nil)
|
31
|
+
subject._run!
|
32
|
+
subject.result.should == :failure
|
33
|
+
subject.message.should == "invalid_request"
|
34
|
+
subject.error_status.should == 400
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should fail if the access token is expired' do
|
38
|
+
token_instance = double(:respond_to? => true, :expired? => true, :scope? => true)
|
39
|
+
subject.stub(:token).and_return(token_instance)
|
40
|
+
subject._run!
|
41
|
+
subject.result.should == :failure
|
42
|
+
subject.message.should == "invalid_token"
|
43
|
+
subject.error_status.should == 401
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should fail if there is insufficient scope' do
|
47
|
+
token_instance = double(:respond_to? => true, :expired? => false, :scope? => false)
|
48
|
+
subject.stub(:token).and_return(token_instance)
|
49
|
+
subject.stub(:scope).and_return(:secret)
|
50
|
+
subject._run!
|
51
|
+
subject.result.should == :failure
|
52
|
+
subject.message.should == "invalid_scope"
|
53
|
+
subject.error_status.should == 403
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/warden/oauth2/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["AirService"]
|
6
|
+
gem.email = ["devs@airservice.co"]
|
7
|
+
gem.description = %q{OAuth 2.0 strategies for Warden}
|
8
|
+
gem.summary = %q{OAuth 2.0 strategies for Warden}
|
9
|
+
gem.homepage = "https://github.com/airservice/warden-oauth2"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "warden-oauth2-strategies"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Warden::OAuth2::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'warden'
|
19
|
+
gem.add_development_dependency 'rake'
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
|
+
gem.add_development_dependency 'rack-test'
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: warden-oauth2-strategies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- AirService
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: warden
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack-test
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: OAuth 2.0 strategies for Warden
|
70
|
+
email:
|
71
|
+
- devs@airservice.co
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- .travis.yml
|
79
|
+
- Gemfile
|
80
|
+
- Guardfile
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/warden-oauth2.rb
|
84
|
+
- lib/warden/oauth2.rb
|
85
|
+
- lib/warden/oauth2/error_app.rb
|
86
|
+
- lib/warden/oauth2/failure_app.rb
|
87
|
+
- lib/warden/oauth2/strategies/base.rb
|
88
|
+
- lib/warden/oauth2/strategies/bearer.rb
|
89
|
+
- lib/warden/oauth2/strategies/client.rb
|
90
|
+
- lib/warden/oauth2/strategies/client_credentials.rb
|
91
|
+
- lib/warden/oauth2/strategies/public.rb
|
92
|
+
- lib/warden/oauth2/strategies/resource_owner_password_credentials.rb
|
93
|
+
- lib/warden/oauth2/strategies/token.rb
|
94
|
+
- lib/warden/oauth2/version.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
- spec/warden/oauth2/failure_app_spec.rb
|
97
|
+
- spec/warden/oauth2/strategies/bearer_spec.rb
|
98
|
+
- spec/warden/oauth2/strategies/client_credentials_spec.rb
|
99
|
+
- spec/warden/oauth2/strategies/client_spec.rb
|
100
|
+
- spec/warden/oauth2/strategies/public_spec.rb
|
101
|
+
- spec/warden/oauth2/strategies/resource_owner_password_credentials_spec.rb
|
102
|
+
- spec/warden/oauth2/strategies/token_spec.rb
|
103
|
+
- warden-oauth2.gemspec
|
104
|
+
homepage: https://github.com/airservice/warden-oauth2
|
105
|
+
licenses: []
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.2.1
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: OAuth 2.0 strategies for Warden
|
127
|
+
test_files:
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
- spec/warden/oauth2/failure_app_spec.rb
|
130
|
+
- spec/warden/oauth2/strategies/bearer_spec.rb
|
131
|
+
- spec/warden/oauth2/strategies/client_credentials_spec.rb
|
132
|
+
- spec/warden/oauth2/strategies/client_spec.rb
|
133
|
+
- spec/warden/oauth2/strategies/public_spec.rb
|
134
|
+
- spec/warden/oauth2/strategies/resource_owner_password_credentials_spec.rb
|
135
|
+
- spec/warden/oauth2/strategies/token_spec.rb
|