rack-oauth2-server 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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, you can require +rack/oauth2/rails+ and configure the server
28
- from +config/environment.rb+. For example:
29
-
30
- require "rack/oauth2/rails"
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[:database] = Mongo::Connection.new["my_db"]
34
- config.oauth[:scopes] = %w{read write}
35
- config.oauth[:authenticator] = lambda do |username, password|
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, you can require +rack/oauth2/sinatra+ and register
44
- {Rack::OAuth2::Sinatra} into your application. For example:
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[:database] = Mongo::Connection.new["my_db"]
52
- oauth[:scopes] = %w{read write}
53
- oauth[:authenticator] = lambda do |username, password|
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+ with a hash of
62
- configuration options.
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 resource string (e.g. user ID) or nil.
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+. {Rack::OAuth2::Server}
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, {Rack::OAuth2::Server} sets the request
96
- header +oauth.authorization+ to the authorization handle, and passes control to
97
- your application. Your application will ask the user to grant or deny the
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.resource+ to the authorized resource. This is
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. {Rack::OAuth2::Server} intercepts this response and redirects
105
- the user back to the client application with an authorization code or access
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). {Rack::OAuth2::Server} will then redirect the user back to
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
- @authorization = oauth.authorization
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
- @authorization = oauth.authorization
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(@client.display_name), @client.link %>
156
- is requesting to <%= @scope.to_sentence %> your account.</h2>
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="<%= @authorization %>">
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="<%= @authorization %>">
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
- {Rack::OAuth2::Server} intercepts all incoming requests and looks for either
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.resource+ to the value you supplied during authorization (step 3).
167
+ +oauth.identity+ to the value you supplied during authorization (step 3).
173
168
 
174
- You can use +oauth.resource+ to resolve the access token back to user, account
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.resource) if oauth.authenticated?
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.resource) if oauth.authenticated?
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 {Rack::OAuth2::Server} and your application, and the protocol the
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.resource |
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.resource | to client app
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 resource.
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 a resource with the given scope. It
408
- keeps track of the account identifier (supplied by the application), client
409
- identifier and scope (both supplied by the client).
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
- {Rack::OAuth2::Server} was written to provide authorization/authentication for
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
- {Rack::OAuth2::Server} is available under the MIT license.
419
+ Rack::OAuth2::Server is available under the MIT license.
@@ -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(resource, scope, client_id, redirect_uri)
16
- fields = { :_id=>Server.secure_random, :resource=>resource, :scope=>scope, :client_id=>client_id, :redirect_uri=>redirect_uri,
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 resource we authorized access to.
31
- attr_reader :resource
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(resource, scope, client_id)
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
- # Allows us to kill all pending grants on behalf of client/resource.
69
- #collection.create_index [[:client_id, Mongo::ASCENDING]]
70
- #collection.create_index [[:resource, Mongo::ASCENDING]]
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, a resource
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(resource, scope, client_id)
18
- unless token = collection.find_one({ :resource=>resource, :scope=>scope, :client_id=>client_id })
19
- token = { :_id=>Server.secure_random, :resource=>resource, :scope=>scope, :client_id=>client_id,
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 a resource.
27
- def from_resource(resource)
28
- collection.find({ :resource=>resource }).map { |fields| Server.new_instance self, fields }
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 resource we authorized access to.
40
- attr_reader :resource
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
- # Allows us to kill all pending grants on behalf of client/resource.
59
- #collection.create_index [[:client_id, Mongo::ASCENDING]]
60
- #collection.create_index [[:resource, Mongo::ASCENDING]]
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 resource.
56
- def grant!(resource)
57
- raise ArgumentError, "Must supply a resource" unless resource
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(resource, scope, client_id, redirect_uri)
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(resource, scope, client_id)
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
- # Allows us to kill all pending request on behalf of client.
83
- #collection.create_index [[:client_id, Mongo::ASCENDING]]
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
- #collection.create_index [[:display_name, Mongo::ASCENDING]]
68
- #collection.create_index [[:link, Mongo::ASCENDING]]
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