rack-oauth2-server 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +12 -0
- data/README.rdoc +44 -48
- data/lib/rack/oauth2/models.rb +20 -7
- data/lib/rack/oauth2/models/access_grant.rb +9 -8
- data/lib/rack/oauth2/models/access_token.rb +16 -12
- data/lib/rack/oauth2/models/auth_request.rb +10 -7
- data/lib/rack/oauth2/models/client.rb +14 -2
- data/lib/rack/oauth2/rails.rb +2 -13
- data/lib/rack/oauth2/server.rb +35 -30
- data/lib/rack/oauth2/server/helper.rb +13 -13
- data/lib/rack/oauth2/server/version.rb +1 -1
- data/lib/rack/oauth2/sinatra.rb +4 -4
- data/test/rails/app/controllers/api_controller.rb +2 -2
- data/test/rails/app/controllers/application_controller.rb +0 -2
- data/test/rails/config/environment.rb +6 -6
- data/test/rails/log/test.log +22090 -0
- data/test/sinatra/my_app.rb +5 -5
- metadata +4 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
2010-11-02 1.1.0
|
2
|
+
|
3
|
+
Renamed oauth.resource as oauth.identity to remove confusion, besides, it's
|
4
|
+
more often identity than anything else.
|
5
|
+
|
6
|
+
Added automagic loading under Rails, no need to require special path.
|
7
|
+
|
8
|
+
Added Rack::OAuth2::Server::Options class, easier to user than Hash.
|
9
|
+
|
10
|
+
Added indexes for speedier queries.
|
11
|
+
|
12
|
+
|
1
13
|
2010-11-02 1.0.0
|
2
14
|
|
3
15
|
World premiere.
|
data/README.rdoc
CHANGED
@@ -24,15 +24,15 @@ support a different database engine, send us a pull request.
|
|
24
24
|
|
25
25
|
== Step 2: Use The Server
|
26
26
|
|
27
|
-
For Rails 2.3.x,
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
For Rails 2.3.x, Rack::OAuth2::Server automatically adds itself as middleware
|
28
|
+
when required, but you do need to configure it from within
|
29
|
+
+config/environment.rb+ (or one of the specific environment files). For
|
30
|
+
example:
|
31
31
|
|
32
32
|
Rails::Initializer.run do |config|
|
33
|
-
config.oauth
|
34
|
-
config.oauth
|
35
|
-
config.oauth
|
33
|
+
config.oauth.database = Mongo::Connection.new["my_db"]
|
34
|
+
config.oauth.scopes = %w{read write}
|
35
|
+
config.oauth.authenticator = lambda do |username, password|
|
36
36
|
user = User.find(username)
|
37
37
|
user if user && user.authenticated?(password)
|
38
38
|
end
|
@@ -40,17 +40,17 @@ from +config/environment.rb+. For example:
|
|
40
40
|
. . .
|
41
41
|
end
|
42
42
|
|
43
|
-
For Sinatra and Padrino,
|
44
|
-
|
43
|
+
For Sinatra and Padrino, first require +rack/oauth2/sinatra+ and register
|
44
|
+
Rack::OAuth2::Sinatra into your application. For example:
|
45
45
|
|
46
46
|
require "rack/oauth2/sinatra"
|
47
47
|
|
48
48
|
class MyApp < Sinatra::Base
|
49
49
|
register Rack::OAuth2::Sinatra
|
50
50
|
|
51
|
-
oauth
|
52
|
-
oauth
|
53
|
-
oauth
|
51
|
+
oauth.database = Mongo::Connection.new["my_db"]
|
52
|
+
oauth.scopes = %w{read write}
|
53
|
+
oauth.authenticator = lambda do |username, password|
|
54
54
|
user = User.find(username)
|
55
55
|
user if user && user.authenticated?(password)
|
56
56
|
end
|
@@ -58,15 +58,15 @@ For Sinatra and Padrino, you can require +rack/oauth2/sinatra+ and register
|
|
58
58
|
. . .
|
59
59
|
end
|
60
60
|
|
61
|
-
With any other Rack server, you can +use Rack::OAuth2::Server+
|
62
|
-
|
61
|
+
With any other Rack server, you can +use Rack::OAuth2::Server+ and pass your
|
62
|
+
own {Rack::OAuth2::Server::Options} object.
|
63
63
|
|
64
64
|
The configuration options are:
|
65
65
|
|
66
66
|
- +:access_token_path+ -- Path for requesting access token. By convention
|
67
67
|
defaults to +/oauth/access_token+.
|
68
68
|
- +:authenticator+ -- For username/password authorization. A block that
|
69
|
-
receives the credentials and returns
|
69
|
+
receives the credentials and returns identity string (e.g. user ID) or nil.
|
70
70
|
- +:authorization_types+ -- Array of supported authorization types. Defaults to
|
71
71
|
["code", "token"], and you can change it to just one of these names.
|
72
72
|
- +:authorize_path+ -- Path for requesting end-user authorization. By
|
@@ -87,36 +87,33 @@ quite handy during development/testing.
|
|
87
87
|
|
88
88
|
== Step 3: Let Users Authorize
|
89
89
|
|
90
|
-
Authorization requests go to +/oauth/authorize+.
|
90
|
+
Authorization requests go to +/oauth/authorize+. Rack::OAuth2::Server
|
91
91
|
intercepts these requests and validates the client ID, secret, redirect URI,
|
92
92
|
authorization type and scope. If the request fails validation, the user is
|
93
93
|
redirected back to the client application with a suitable error code.
|
94
94
|
|
95
|
-
If the request passes validation,
|
96
|
-
|
97
|
-
|
95
|
+
If the request passes validation, Rack::OAuth2::Server sets the request header
|
96
|
+
+oauth.authorization+ to the authorization handle, and passes control to your
|
97
|
+
application. Your application will ask the user to grant or deny the
|
98
98
|
authorization request.
|
99
99
|
|
100
100
|
Once granted, your application signals the grant by setting the response header
|
101
101
|
+oauth.authorization+ to the authorization handle it got before, and setting
|
102
|
-
the response header +oauth.
|
102
|
+
the response header +oauth.identity+ to the authorized identity. This is
|
103
103
|
typicaly the user ID or account ID, but can be anything you want, as long as
|
104
|
-
it's a string.
|
105
|
-
|
106
|
-
token.
|
104
|
+
it's a string. Rack::OAuth2::Server intercepts this response and redirects the
|
105
|
+
user back to the client application with an authorization code or access token.
|
107
106
|
|
108
107
|
To signal that the user denied the authorization requests your application sets
|
109
108
|
the response header oauth.authorization as before, and returns the status code
|
110
|
-
401 (Unauthorized).
|
109
|
+
401 (Unauthorized). Rack::OAuth2::Server will then redirect the user back to
|
111
110
|
the client application with a suitable error code.
|
112
111
|
|
113
112
|
In Rails, the entire flow would look something like this:
|
114
113
|
|
115
114
|
class OauthController < ApplicationController
|
116
115
|
def authorize
|
117
|
-
|
118
|
-
@client = oauth.client
|
119
|
-
@scope = oauth.scope
|
116
|
+
oauth.authorization ||= params[:authorization]
|
120
117
|
end
|
121
118
|
|
122
119
|
def grant
|
@@ -136,9 +133,7 @@ method.
|
|
136
133
|
In Sinatra/Padrino, it would look something like this:
|
137
134
|
|
138
135
|
get "/oauth/authorize" do
|
139
|
-
|
140
|
-
@client = oauth.client
|
141
|
-
@scope = oauth.scope
|
136
|
+
oauth.authorization ||= params[:authorization]
|
142
137
|
render "oauth/authorize"
|
143
138
|
end
|
144
139
|
|
@@ -152,26 +147,26 @@ In Sinatra/Padrino, it would look something like this:
|
|
152
147
|
|
153
148
|
The view would look something like this:
|
154
149
|
|
155
|
-
<h2>The application <% link_to h(
|
156
|
-
is requesting to <%=
|
150
|
+
<h2>The application <% link_to h(oauth.client.display_name), oauth.client.link %>
|
151
|
+
is requesting to <%= oauth.scope.to_sentence %> your account.</h2>
|
157
152
|
<form action="/oauth/grant">
|
158
153
|
<button>Grant</button>
|
159
|
-
<input type="hidden" name="authorization" value="<%=
|
154
|
+
<input type="hidden" name="authorization" value="<%= oauth.authorization %>">
|
160
155
|
</form>
|
161
156
|
<form action="/oauth/deny">
|
162
157
|
<button>Deny</button>
|
163
|
-
<input type="hidden" name="authorization" value="<%=
|
158
|
+
<input type="hidden" name="authorization" value="<%= oauth.authorization %>">
|
164
159
|
</form>
|
165
160
|
|
166
161
|
|
167
162
|
== Step 4: Protect Your Path
|
168
163
|
|
169
|
-
|
164
|
+
Rack::OAuth2::Server intercepts all incoming requests and looks for either
|
170
165
|
OAuth authentication header, or +oauth_token+ parameter. If it finds either
|
171
166
|
one, and the access token is still valid, it sets the request header
|
172
|
-
+oauth.
|
167
|
+
+oauth.identity+ to the value you supplied during authorization (step 3).
|
173
168
|
|
174
|
-
You can use +oauth.
|
169
|
+
You can use +oauth.identity+ to resolve the access token back to user, account
|
175
170
|
or whatever you put there.
|
176
171
|
|
177
172
|
If the access token is invalid or revoked, it returns 401 (Unauthorized) to the
|
@@ -221,7 +216,7 @@ In Rails, it would look something like this:
|
|
221
216
|
protected
|
222
217
|
|
223
218
|
def set_current_user
|
224
|
-
@current_user = User.find(oauth.
|
219
|
+
@current_user = User.find(oauth.identity) if oauth.authenticated?
|
225
220
|
end
|
226
221
|
|
227
222
|
end
|
@@ -229,7 +224,7 @@ In Rails, it would look something like this:
|
|
229
224
|
In Sinatra/Padrino, it would look something like this:
|
230
225
|
|
231
226
|
before do
|
232
|
-
@current_user = User.find(oauth.
|
227
|
+
@current_user = User.find(oauth.identity) if oauth.authenticated?
|
233
228
|
end
|
234
229
|
|
235
230
|
oauth_required "/private"
|
@@ -335,7 +330,7 @@ production access tokens.
|
|
335
330
|
== Mandatory ASCII Diagram
|
336
331
|
|
337
332
|
This is briefly what the authorization flow looks like, how the workload is
|
338
|
-
split between
|
333
|
+
split between Rack::OAuth2::Server and your application, and the protocol the
|
339
334
|
two use to control the authorization flow:
|
340
335
|
|
341
336
|
Rack::OAuth2::Server
|
@@ -350,14 +345,14 @@ two use to control the authorization flow:
|
|
350
345
|
| Authenticate user | | Ask user to grant/ | | Set response |
|
351
346
|
-> | | -> | deny client access | -> | | ->
|
352
347
|
| | | to their account | | oauth.authorization |
|
353
|
-
| | | | | oauth.
|
348
|
+
| | | | | oauth.identity |
|
354
349
|
-------------------- ---------------------- -----------------------
|
355
350
|
|
356
351
|
Rack::OAuth2::Server
|
357
352
|
-----------------------
|
358
353
|
| Create access grant |
|
359
354
|
-> | or access token for | -> Redirect back
|
360
|
-
| oauth.
|
355
|
+
| oauth.identity | to client app
|
361
356
|
-----------------------
|
362
357
|
|
363
358
|
|
@@ -394,7 +389,7 @@ identifier.
|
|
394
389
|
|
395
390
|
Granting an authorization request (by calling +grant!+) creates an access grant or
|
396
391
|
access token, depending on the requested response type, and associates it with
|
397
|
-
the
|
392
|
+
the identity.
|
398
393
|
|
399
394
|
=== Access Grant
|
400
395
|
|
@@ -404,9 +399,10 @@ code") and all the data it needs to create an access token.
|
|
404
399
|
|
405
400
|
=== Access Token
|
406
401
|
|
407
|
-
An access token allows the client to access
|
408
|
-
|
409
|
-
identifier and scope (both supplied by
|
402
|
+
An access token allows the client to access the resource with the given scope
|
403
|
+
on behalf of a given identity. It keeps track of the account identifier
|
404
|
+
(supplied by the application), client identifier and scope (both supplied by
|
405
|
+
the client).
|
410
406
|
|
411
407
|
An {Rack::OAuth2::Server::AccessToken} is created by copying values from an
|
412
408
|
+AuthRequest+ or +AccessGrant+, and remains in effect until revoked. (OAuth 2.0
|
@@ -415,9 +411,9 @@ access tokens can also expire, but we don't support expiration at the moment)
|
|
415
411
|
|
416
412
|
== Credits
|
417
413
|
|
418
|
-
|
414
|
+
Rack::OAuth2::Server was written to provide authorization/authentication for
|
419
415
|
the new Flowtown API[http://developer.flowtown.com]. Thanks to
|
420
416
|
Flowtown[http://flowtown.com] for making it happen and allowing it to be open
|
421
417
|
sourced.
|
422
418
|
|
423
|
-
|
419
|
+
Rack::OAuth2::Server is available under the MIT license.
|
data/lib/rack/oauth2/models.rb
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
require "mongo"
|
2
2
|
require "openssl"
|
3
3
|
|
4
|
-
|
5
|
-
require "rack/oauth2/models/client"
|
6
|
-
require "rack/oauth2/models/auth_request"
|
7
|
-
require "rack/oauth2/models/access_grant"
|
8
|
-
require "rack/oauth2/models/access_token"
|
9
|
-
|
10
4
|
module Rack
|
11
5
|
module OAuth2
|
12
6
|
class Server
|
@@ -29,9 +23,28 @@ module Rack
|
|
29
23
|
def secure_random
|
30
24
|
OpenSSL::Random.random_bytes(32).unpack("H*")[0]
|
31
25
|
end
|
32
|
-
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def create_indexes(&block)
|
29
|
+
if block
|
30
|
+
@create_indexes ||= []
|
31
|
+
@create_indexes << block
|
32
|
+
elsif @create_indexes
|
33
|
+
@create_indexes.each do |block|
|
34
|
+
block.call
|
35
|
+
end
|
36
|
+
@create_indexes = nil
|
37
|
+
end
|
38
|
+
end
|
33
39
|
end
|
34
40
|
|
35
41
|
end
|
36
42
|
end
|
37
43
|
end
|
44
|
+
|
45
|
+
|
46
|
+
require "rack/oauth2/models/client"
|
47
|
+
require "rack/oauth2/models/auth_request"
|
48
|
+
require "rack/oauth2/models/access_grant"
|
49
|
+
require "rack/oauth2/models/access_token"
|
50
|
+
|
@@ -12,8 +12,8 @@ module Rack
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Create a new access grant.
|
15
|
-
def create(
|
16
|
-
fields = { :_id=>Server.secure_random, :
|
15
|
+
def create(identity, scope, client_id, redirect_uri)
|
16
|
+
fields = { :_id=>Server.secure_random, :identity=>identity.to_s, :scope=>scope, :client_id=>client_id, :redirect_uri=>redirect_uri,
|
17
17
|
:created_at=>Time.now.utc, :granted_at=>nil, :access_token=>nil, :revoked=>nil }
|
18
18
|
collection.insert fields
|
19
19
|
Server.new_instance self, fields
|
@@ -27,8 +27,8 @@ module Rack
|
|
27
27
|
# Authorization code. We are nothing without it.
|
28
28
|
attr_reader :_id
|
29
29
|
alias :code :_id
|
30
|
-
# The
|
31
|
-
attr_reader :
|
30
|
+
# The identity we authorized access to.
|
31
|
+
attr_reader :identity
|
32
32
|
# Client that was granted this access token.
|
33
33
|
attr_reader :client_id
|
34
34
|
# Redirect URI for this grant.
|
@@ -52,7 +52,7 @@ module Rack
|
|
52
52
|
# InvalidGrantError.
|
53
53
|
def authorize!
|
54
54
|
raise InvalidGrantError if self.access_token || self.revoked
|
55
|
-
access_token = AccessToken.get_token_for(
|
55
|
+
access_token = AccessToken.get_token_for(identity, scope, client_id)
|
56
56
|
self.access_token = access_token.token
|
57
57
|
self.granted_at = Time.now.utc
|
58
58
|
self.class.collection.update({ :_id=>code, :access_token=>nil, :revoked=>nil }, { :$set=>{ :granted_at=>granted_at, :access_token=>access_token.token } }, :safe=>true)
|
@@ -65,9 +65,10 @@ module Rack
|
|
65
65
|
self.class.collection.update({ :_id=>code, :revoked=>nil }, { :$set=>{ :revoked=>Time.now.utc } })
|
66
66
|
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
Server.create_indexes do
|
69
|
+
# Used to revoke all pending access grants when revoking client.
|
70
|
+
collection.create_index [[:client_id, Mongo::ASCENDING]]
|
71
|
+
end
|
71
72
|
end
|
72
73
|
|
73
74
|
end
|
@@ -4,7 +4,7 @@ module Rack
|
|
4
4
|
|
5
5
|
# Access token. This is what clients use to access resources.
|
6
6
|
#
|
7
|
-
# An access token is a unique code, associated with a client,
|
7
|
+
# An access token is a unique code, associated with a client, an identity
|
8
8
|
# and scope. It may be revoked, or expire after a certain period.
|
9
9
|
class AccessToken
|
10
10
|
class << self
|
@@ -14,18 +14,18 @@ module Rack
|
|
14
14
|
end
|
15
15
|
|
16
16
|
# Get an access token (create new one if necessary).
|
17
|
-
def get_token_for(
|
18
|
-
unless token = collection.find_one({ :
|
19
|
-
token = { :_id=>Server.secure_random, :
|
17
|
+
def get_token_for(identity, scope, client_id)
|
18
|
+
unless token = collection.find_one({ :identity=>identity.to_s, :scope=>scope, :client_id=>client_id })
|
19
|
+
token = { :_id=>Server.secure_random, :identity=>identity.to_s, :scope=>scope, :client_id=>client_id,
|
20
20
|
:created_at=>Time.now.utc, :expires_at=>nil, :revoked=>nil }
|
21
21
|
collection.insert token
|
22
22
|
end
|
23
23
|
Server.new_instance self, token
|
24
24
|
end
|
25
25
|
|
26
|
-
# Find all AccessTokens for
|
27
|
-
def
|
28
|
-
collection.find({ :
|
26
|
+
# Find all AccessTokens for an identity.
|
27
|
+
def from_identity(identity)
|
28
|
+
collection.find({ :identity=>identity }).map { |fields| Server.new_instance self, fields }
|
29
29
|
end
|
30
30
|
|
31
31
|
def collection
|
@@ -36,8 +36,8 @@ module Rack
|
|
36
36
|
# Access token. As unique as they come.
|
37
37
|
attr_reader :_id
|
38
38
|
alias :token :_id
|
39
|
-
# The
|
40
|
-
attr_reader :
|
39
|
+
# The identity we authorized access to.
|
40
|
+
attr_reader :identity
|
41
41
|
# Client that was granted this access token.
|
42
42
|
attr_reader :client_id
|
43
43
|
# The scope granted in this token.
|
@@ -55,9 +55,13 @@ module Rack
|
|
55
55
|
AccessToken.collection.update({ :_id=>token }, { :$set=>{ :revoked=>revoked } })
|
56
56
|
end
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
Server.create_indexes do
|
59
|
+
# Used to revoke all pending access grants when revoking client.
|
60
|
+
collection.create_index [[:client_id, Mongo::ASCENDING]]
|
61
|
+
# Used to get/revoke access tokens for an identity, also to find and
|
62
|
+
# return existing access token.
|
63
|
+
collection.create_index [[:identity, Mongo::ASCENDING]]
|
64
|
+
end
|
61
65
|
end
|
62
66
|
|
63
67
|
end
|
@@ -52,20 +52,20 @@ module Rack
|
|
52
52
|
# Timestamp if revoked.
|
53
53
|
attr_accessor :revoked
|
54
54
|
|
55
|
-
# Grant access to the specified
|
56
|
-
def grant!(
|
57
|
-
raise ArgumentError, "Must supply a
|
55
|
+
# Grant access to the specified identity.
|
56
|
+
def grant!(identity)
|
57
|
+
raise ArgumentError, "Must supply a identity" unless identity
|
58
58
|
return if revoked
|
59
59
|
self.authorized_at = Time.now.utc
|
60
60
|
if response_type == "code" # Requested authorization code
|
61
61
|
unless self.grant_code
|
62
|
-
access_grant = AccessGrant.create(
|
62
|
+
access_grant = AccessGrant.create(identity, scope, client_id, redirect_uri)
|
63
63
|
self.grant_code = access_grant.code
|
64
64
|
self.class.collection.update({ :_id=>id, :revoked=>nil }, { :$set=>{ :grant_code=>access_grant.code, :authorized_at=>authorized_at } })
|
65
65
|
end
|
66
66
|
else # Requested access token
|
67
67
|
unless self.access_token
|
68
|
-
access_token = AccessToken.get_token_for(
|
68
|
+
access_token = AccessToken.get_token_for(identity, scope, client_id)
|
69
69
|
self.access_token = access_token.token
|
70
70
|
self.class.collection.update({ :_id=>id, :revoked=>nil, :access_token=>nil }, { :$set=>{ :access_token=>access_token.token, :authorized_at=>authorized_at } })
|
71
71
|
end
|
@@ -79,8 +79,11 @@ module Rack
|
|
79
79
|
self.class.collection.update({ :_id=>id }, { :$set=>{ :authorized_at=>authorized_at } })
|
80
80
|
end
|
81
81
|
|
82
|
-
|
83
|
-
|
82
|
+
Server.create_indexes do
|
83
|
+
# Used to revoke all pending access grants when revoking client.
|
84
|
+
collection.create_index [[:client_id, Mongo::ASCENDING]]
|
85
|
+
end
|
86
|
+
|
84
87
|
end
|
85
88
|
|
86
89
|
end
|
@@ -30,6 +30,14 @@ module Rack
|
|
30
30
|
Server.new_instance self, fields
|
31
31
|
end
|
32
32
|
|
33
|
+
# Lookup client by ID, display name or URL.
|
34
|
+
def lookup(field)
|
35
|
+
id = BSON::ObjectId(field.to_s)
|
36
|
+
Server.new_instance self, collection.find_one(id)
|
37
|
+
rescue BSON::InvalidObjectId
|
38
|
+
Server.new_instance self, collection.find_one({ :display_name=>field }) || collection.find_one({ :link=>field })
|
39
|
+
end
|
40
|
+
|
33
41
|
def collection
|
34
42
|
Server.database["oauth2.clients"]
|
35
43
|
end
|
@@ -64,8 +72,12 @@ module Rack
|
|
64
72
|
AccessToken.collection.update({ :client_id=>id }, { :$set=>{ :revoked=>revoked } })
|
65
73
|
end
|
66
74
|
|
67
|
-
|
68
|
-
|
75
|
+
Server.create_indexes do
|
76
|
+
# For quickly returning clients sorted by display name, or finding
|
77
|
+
# client from a URL.
|
78
|
+
collection.create_index [[:display_name, Mongo::ASCENDING]]
|
79
|
+
collection.create_index [[:link, Mongo::ASCENDING]]
|
80
|
+
end
|
69
81
|
end
|
70
82
|
|
71
83
|
end
|