rack-oauth2-server 2.0.0.beta → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,4 +1,4 @@
1
- 2010-11-15 version 2.0.0
1
+ 2010-11-16 version 2.0.0
2
2
 
3
3
  Major change: you are no longer able to get an access token with the scope if
4
4
  the client is not registered to have this scope. The global setting scopes is
@@ -7,6 +7,9 @@ clients, e.g. in mogno console:
7
7
 
8
8
  db.oauth2.clients.update({}, { $set: { scopes: ["read", "write"] } }, true, true)
9
9
 
10
+ Rack::OAuth2::Server class methods get register -- for registering and updating
11
+ client application record -- and get_token_for -- to obtain new/existing token.
12
+
10
13
  Web console now allows you to set/unset individual scopes for each client
11
14
  application.
12
15
 
data/README.rdoc CHANGED
@@ -291,14 +291,27 @@ Programatically, registering a new client is as simple as:
291
291
 
292
292
  $ ./script/console
293
293
  Loading development environment (Rails 2.3.8)
294
- > uber_client = Rack::OAuth2::Server::Client.create(:display_name=>"UberClient",
295
- :link=>"http://uberclient.dot/",
294
+ > Rack::OAuth2::Server.register(:display_name=>"UberClient",
295
+ :link=>"http://example.com/",
296
296
  :image_url=>"http://farm5.static.flickr.com/4122/4890273282_58f7c345f4.jpg",
297
297
  :scopes=>%{read write},
298
- :redirect_uri=>"http://uberclient.dot/oauth/callback")
298
+ :redirect_uri=>"http://example.com/oauth/callback")
299
299
  > puts "Your client identifier: #{client.id}"
300
300
  > puts "Your client secret: #{client.secret}"
301
301
 
302
+ You may want your application to register its own client application, always
303
+ with the same client ID and secret, which are also stored in a configuration
304
+ file. For example, your +db/seed.rb+ may contain:
305
+
306
+ oauth2 = YAML.load_file(Rails.root + "config/oauth2.yml")
307
+ Rack::OAuth2::Server.register(id: oauth2["client_id"], secret: oauth2["client_secret"],
308
+ display_name: "UberClient", link: "http://example.com",
309
+ redirect_uri: "http://example.com/oauth/callback", scopes: oauth2["scopes"].split)
310
+
311
+ When you call +register+ with +id+ and +secret+ parameters it either registers a
312
+ new client with these specific ID and sceret, or if a client already exists,
313
+ updates its other properties.
314
+
302
315
 
303
316
  === Step 6: Pimp Your API
304
317
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.beta
1
+ 2.0.0.beta2
@@ -4,6 +4,11 @@
4
4
  // Sammy.OAuth2 is a plugin for using OAuth 2.0 to authenticate users and
5
5
  // access your application's API. Requires Sammy.Session.
6
6
  //
7
+ // Triggers the following events:
8
+ // - oauth.connected -- Access token set and ready to use
9
+ // - oauth.disconnected -- Access token reset
10
+ // - oauth.denied -- Authorization attempt rejected
11
+ //
7
12
  // ### Example
8
13
  // this.use(Sammy.Storage);
9
14
  // this.use(Sammy.OAuth2);
@@ -33,8 +38,8 @@
33
38
  // context.redirect("#/");
34
39
  // });
35
40
  //
36
- Sammy.OAuth2 = function(options) {
37
- this.options = options || {};
41
+ Sammy.OAuth2 = function(app) {
42
+ app.use(Sammy.JSON);
38
43
  this.authorize = "/oauth/authorize";
39
44
 
40
45
  // Use this on request that require OAuth token. You can use this in a
@@ -71,20 +76,20 @@
71
76
  // Stores the access token in the session.
72
77
  this.setAccessToken = function(token) {
73
78
  this.session("oauth.token", token);
79
+ this.trigger("oauth.connected");
74
80
  }
75
81
  // Lose access token: use this to sign out.
76
82
  this.loseAccessToken = function() {
77
83
  this.session("oauth.token", null);
84
+ this.trigger("oauth.disconnected");
78
85
  }
79
86
 
80
87
  // Add OAuth 2.0 access token to all XHR requests.
81
- $(document).ajaxSend(function(oauth) {
82
- return function(evt, xhr) {
83
- var token = oauth.getAccessToken();
84
- if (token)
85
- xhr.setRequestHeader("Authorization", "OAuth " + token);
86
- }
87
- }(this));
88
+ $(document).ajaxSend(function(evt, xhr) {
89
+ var token = app.getAccessToken();
90
+ if (token)
91
+ xhr.setRequestHeader("Authorization", "OAuth " + token);
92
+ });
88
93
 
89
94
  // Converts query string parameters in fragment identifier to object.
90
95
  function parseParams(hash) {
@@ -101,6 +106,8 @@
101
106
  // redirection.
102
107
  this.bind("run", function(evt, params) {
103
108
  start_url = params.start_url || "#";
109
+ if (this.app.getAccessToken())
110
+ this.trigger("oauth.connected");
104
111
  });
105
112
 
106
113
  // Intercept OAuth authorization response with access token, stores it and
@@ -53,7 +53,7 @@ module Rack
53
53
  # InvalidGrantError.
54
54
  def authorize!
55
55
  raise InvalidGrantError if self.access_token || self.revoked
56
- access_token = AccessToken.get_token_for(identity, scope, client_id)
56
+ access_token = AccessToken.get_token_for(identity, client_id, scope)
57
57
  self.access_token = access_token.token
58
58
  self.granted_at = Time.now.utc.to_i
59
59
  self.class.collection.update({ :_id=>code, :access_token=>nil, :revoked=>nil }, { :$set=>{ :granted_at=>granted_at, :access_token=>access_token.token } }, :safe=>true)
@@ -15,7 +15,7 @@ module Rack
15
15
  end
16
16
 
17
17
  # Get an access token (create new one if necessary).
18
- def get_token_for(identity, scope, client_id)
18
+ def get_token_for(identity, client_id, scope)
19
19
  scope = Utils.normalize_scopes(scope)
20
20
  client_id = BSON::ObjectId(client_id.to_s)
21
21
  unless token = collection.find_one({ :identity=>identity.to_s, :scope=>scope, :client_id=>client_id, :revoked=>nil })
@@ -63,7 +63,7 @@ module Rack
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
  else # Requested access token
66
- access_token = AccessToken.get_token_for(identity, scope, client_id)
66
+ access_token = AccessToken.get_token_for(identity, client_id, scope)
67
67
  self.access_token = access_token.token
68
68
  self.class.collection.update({ :_id=>id, :revoked=>nil, :access_token=>nil }, { :$set=>{ :access_token=>access_token.token, :authorized_at=>authorized_at } })
69
69
  end
@@ -26,10 +26,16 @@ module Rack
26
26
  def create(args)
27
27
  redirect_uri = Server::Utils.parse_redirect_uri(args[:redirect_uri]).to_s if args[:redirect_uri]
28
28
  scopes = Server::Utils.normalize_scopes(args[:scopes])
29
- fields = { :secret=>Server.secure_random, :display_name=>args[:display_name], :link=>args[:link],
29
+ fields = { :display_name=>args[:display_name], :link=>args[:link],
30
30
  :image_url=>args[:image_url], :redirect_uri=>redirect_uri, :scopes=>scopes,
31
31
  :created_at=>Time.now.utc.to_i, :revoked=>nil }
32
- fields[:_id] = collection.insert(fields)
32
+ if args[:id] && args[:secret]
33
+ fields[:_id], fields[:secret] = args[:id], args[:secret]
34
+ collection.insert(fields)
35
+ else
36
+ fields[:secret] = Server.secure_random
37
+ fields[:_id] = collection.insert(fields)
38
+ end
33
39
  Server.new_instance self, fields
34
40
  end
35
41
 
@@ -16,21 +16,85 @@ module Rack
16
16
 
17
17
  class << self
18
18
  # Return AuthRequest from authorization request handle.
19
+ #
20
+ # @param [String] authorization Authorization handle (e.g. from
21
+ # oauth.authorization)
22
+ # @return [AuthReqeust]
19
23
  def get_auth_request(authorization)
20
24
  AuthRequest.find(authorization)
21
25
  end
22
26
 
23
27
  # Returns Client from client identifier.
28
+ #
29
+ # @param [String] client_id Client identifier (e.g. from oauth.client.id)
30
+ # @return [Client]
24
31
  def get_client(client_id)
25
32
  Client.find(client_id)
26
33
  end
27
34
 
35
+ # Registers and returns a new Client. Can also be used to update
36
+ # existing client registration, by passing identifier (and secret) of
37
+ # existing client record. That way, your setup script can create a new
38
+ # client application and run repeatedly without fail. Also useful for
39
+ # adding new scopes to your existing client application.
40
+ #
41
+ # @param [Hash] args Arguments for registering client application
42
+ # @option args [String] :id Client identifier. Use this to update
43
+ # existing client registration (in combination wih secret)
44
+ # @option args [String] :secret Client secret. Use this to update
45
+ # existing client registration.
46
+ # @option args [String] :display_name Name to show when authorizing
47
+ # access (e.g. "My Awesome Application")
48
+ # @option args [String] link Link to client application's Web site
49
+ # @option args [String] image_url URL of image to show alongside display
50
+ # name.
51
+ # @option args [String] redirect_uri Redirect URL: authorization
52
+ # requests for this client will always redirect back to this URL.
53
+ # @option args [Array] scopes Scopes that client application can request
54
+ #
55
+ # @example Registering new client application
56
+ # Server.register :display_name=>"My Application",
57
+ # :link=>"http://example.com", :scopes=>%w{read write},
58
+ # :redirect_uri=>"http://example.com/oauth/callback"
59
+ # @example Migration using configuration file
60
+ # config = YAML.load_file(Rails.root + "config/oauth.yml")
61
+ # Server.register config["id"], config["secret"],
62
+ # :display_name=>"My Application", :link=>"http://example.com",
63
+ # :scopes=>config["scopes"],
64
+ # :redirect_uri=>"http://example.com/oauth/callback"
65
+ def register(args)
66
+ if args[:id] && args[:secret] && (client = get_client(args[:id]))
67
+ fail "Client secret does not match" unless client.secret == args[:secret]
68
+ client.update args
69
+ else
70
+ Client.create(args)
71
+ end
72
+ end
73
+
28
74
  # Returns AccessToken from token.
75
+ #
76
+ # @param [String] token Access token (e.g. from oauth.access_token)
77
+ # @return [AccessToken]
29
78
  def get_access_token(token)
30
79
  AccessToken.from_token(token)
31
80
  end
32
81
 
82
+ # Returns AccessToken for the specified identity, client application and
83
+ # scopes. You can use this method to request existing access token, new
84
+ # token generated if one does not already exists.
85
+ #
86
+ # @param [String] identity Identity, e.g. user ID, account ID
87
+ # @param [String] client_id Client application identifier
88
+ # @param [String] scope Access scope (e.g. "read write")
89
+ # @return [AccessToken]
90
+ def get_token_for(identity, client_id, scope)
91
+ AccessToken.get_token_for(identity, client_id, scope)
92
+ end
93
+
33
94
  # Returns all AccessTokens for an identity.
95
+ #
96
+ # @param [String] identity Identity, e.g. user ID, account ID
97
+ # @return [Array<AccessToken>]
34
98
  def list_access_tokens(identity)
35
99
  AccessToken.from_identity(identity)
36
100
  end
@@ -267,7 +331,7 @@ module Rack
267
331
  requested_scope = Utils.normalize_scopes(request.POST["scope"])
268
332
  allowed_scopes = client.scopes
269
333
  raise InvalidScopeError unless (requested_scope - allowed_scopes).empty?
270
- access_token = AccessToken.get_token_for(identity, requested_scope, client.id)
334
+ access_token = AccessToken.get_token_for(identity, client.id, requested_scope)
271
335
  else raise UnsupportedGrantType
272
336
  end
273
337
  logger.info "Access token #{access_token.token} granted to client #{client.display_name}, identity #{access_token.identity}" if logger
@@ -18,12 +18,12 @@ class AdminApiTest < Test::Unit::TestCase
18
18
 
19
19
 
20
20
  def without_scope
21
- token = Server::AccessToken.get_token_for("Superman", "nobody", client.id)
21
+ token = Server.get_token_for("Superman", client.id, "nobody")
22
22
  header "Authorization", "OAuth #{token.token}"
23
23
  end
24
24
 
25
25
  def with_scope
26
- token = Server::AccessToken.get_token_for("Superman", "oauth-admin", client.id)
26
+ token = Server.get_token_for("Superman", client.id, "oauth-admin")
27
27
  header "Authorization", "OAuth #{token.token}"
28
28
  end
29
29
 
@@ -149,7 +149,7 @@ class AdminApiTest < Test::Unit::TestCase
149
149
  tokens = []
150
150
  1.upto(10).map do |days|
151
151
  Timecop.travel -days*86400 do
152
- tokens << Server::AccessToken.get_token_for("Superman", days.to_s, client.id)
152
+ tokens << Server.get_token_for("Superman", client.id, days.to_s)
153
153
  end
154
154
  end
155
155
  # Revoke one token today (within past 7 days), one 10 days ago (beyond)
@@ -257,7 +257,7 @@ class AccessTokenTest < Test::Unit::TestCase
257
257
 
258
258
  context "list tokens" do
259
259
  setup do
260
- @other = Rack::OAuth2::Server::AccessToken.get_token_for("foobar", "read", client.id).token
260
+ @other = Rack::OAuth2::Server.get_token_for("foobar", client.id, "read").token
261
261
  get "/list_tokens"
262
262
  end
263
263
 
@@ -273,23 +273,23 @@ class AccessTokenTest < Test::Unit::TestCase
273
273
 
274
274
  context "get_token_for" do
275
275
  should "return two different tokens for two different clients" do
276
- myapp = Rack::OAuth2::Server::AccessToken.get_token_for("Batman", "read write", "4cca30423321e895cb000001")
277
- yourapp = Rack::OAuth2::Server::AccessToken.get_token_for("Batman", "read write", "4fff30423321e895cb000001")
276
+ myapp = Rack::OAuth2::Server.get_token_for("Batman", "4cca30423321e895cb000001", "read write")
277
+ yourapp = Rack::OAuth2::Server.get_token_for("Batman", "4fff30423321e895cb000001", "read write")
278
278
  assert myapp.token != yourapp.token
279
279
  end
280
280
  should "return two different tokens for two different identities" do
281
- me = Rack::OAuth2::Server::AccessToken.get_token_for("Batman", "read write", "4cca30423321e895cb000001")
282
- you = Rack::OAuth2::Server::AccessToken.get_token_for("Robin", "read write", "4cca30423321e895cb000001")
281
+ me = Rack::OAuth2::Server.get_token_for("Batman", "4cca30423321e895cb000001", "read write")
282
+ you = Rack::OAuth2::Server.get_token_for("Robin", "4cca30423321e895cb000001", "read write")
283
283
  assert me.token != you.token
284
284
  end
285
285
  should "return two different tokens for two different scope" do
286
- write = Rack::OAuth2::Server::AccessToken.get_token_for("Batman", "read write", "4cca30423321e895cb000001")
287
- math = Rack::OAuth2::Server::AccessToken.get_token_for("Batman", "read math", "4cca30423321e895cb000001")
286
+ write = Rack::OAuth2::Server.get_token_for("Batman", "4cca30423321e895cb000001", "read write")
287
+ math = Rack::OAuth2::Server.get_token_for("Batman", "4cca30423321e895cb000001", "read math")
288
288
  assert write.token != math.token
289
289
  end
290
290
  should "return same tokens regardless of order of scope" do
291
- one = Rack::OAuth2::Server::AccessToken.get_token_for("Batman", "read write math", "4cca30423321e895cb000001")
292
- two = Rack::OAuth2::Server::AccessToken.get_token_for("Batman", "math write read", "4cca30423321e895cb000001")
291
+ one = Rack::OAuth2::Server.get_token_for("Batman", "4cca30423321e895cb000001", "read write math")
292
+ two = Rack::OAuth2::Server.get_token_for("Batman", "4cca30423321e895cb000001", "math write read")
293
293
  assert_equal one.token, two.token
294
294
  end
295
295
  end
@@ -0,0 +1,203 @@
1
+ require "test/setup"
2
+
3
+
4
+ # Tests the Server API
5
+ class ServerTest < Test::Unit::TestCase
6
+ def setup
7
+ super
8
+ end
9
+
10
+ context "get_auth_request" do
11
+ setup { @request = Server::AuthRequest.create(client.id, client.scopes.join(" "), client.redirect_uri, "token", nil) }
12
+ should "return authorization request" do
13
+ assert_equal @request.id, Server.get_auth_request(@request.id).id
14
+ end
15
+
16
+ should "return nil if no request found" do
17
+ assert !Server.get_auth_request("4ce2488e3321e87ac1000004")
18
+ end
19
+ end
20
+
21
+
22
+ context "get_client" do
23
+ should "return authorization request" do
24
+ assert_equal client.display_name, Server.get_client(client.id).display_name
25
+ end
26
+
27
+ should "return nil if no client found" do
28
+ assert !Server.get_client("4ce2488e3321e87ac1000004")
29
+ end
30
+ end
31
+
32
+
33
+ context "register" do
34
+ context "no client ID" do
35
+ setup do
36
+ @client = Server.register(:display_name=>"MyApp", :link=>"http://example.org", :image_url=>"http://example.org/favicon.ico",
37
+ :redirect_uri=>"http://example.org/oauth/callback", :scopes=>%w{read write})
38
+ end
39
+
40
+ should "create new client" do
41
+ assert_equal 2, Server::Client.collection.count
42
+ assert_contains Server::Client.all.map(&:id), @client.id
43
+ end
44
+
45
+ should "set display name" do
46
+ assert_equal "MyApp", Server.get_client(@client.id).display_name
47
+ end
48
+
49
+ should "set link" do
50
+ assert_equal "http://example.org", Server.get_client(@client.id).link
51
+ end
52
+
53
+ should "set image URL" do
54
+ assert_equal "http://example.org/favicon.ico", Server.get_client(@client.id).image_url
55
+ end
56
+
57
+ should "set redirect URI" do
58
+ assert_equal "http://example.org/oauth/callback", Server.get_client(@client.id).redirect_uri
59
+ end
60
+
61
+ should "set scopes" do
62
+ assert_equal %w{read write}, Server.get_client(@client.id).scopes
63
+ end
64
+
65
+ should "assign client an ID" do
66
+ assert_match /[0-9a-f]{24}/, @client.id.to_s
67
+ end
68
+
69
+ should "assign client a secret" do
70
+ assert_match /[0-9a-f]{64}/, @client.secret
71
+ end
72
+ end
73
+
74
+ context "with client ID" do
75
+
76
+ context "no such client" do
77
+ setup do
78
+ @client = Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"MyApp")
79
+ end
80
+
81
+ should "create new client" do
82
+ assert_equal 2, Server::Client.collection.count
83
+ end
84
+
85
+ should "should assign it the client identifier" do
86
+ assert_equal "4ce24c423321e88ac5000015", @client.id
87
+ end
88
+
89
+ should "should assign it the client secret" do
90
+ assert_equal "foobar", @client.secret
91
+ end
92
+
93
+ should "should assign it the other properties" do
94
+ assert_equal "MyApp", @client.display_name
95
+ end
96
+ end
97
+
98
+ context "existing client" do
99
+ setup do
100
+ Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"MyApp")
101
+ @client = Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"Rock Star")
102
+ end
103
+
104
+ should "not create new client" do
105
+ assert_equal 2, Server::Client.collection.count
106
+ end
107
+
108
+ should "should not change the client identifier" do
109
+ assert_equal "4ce24c423321e88ac5000015", @client.id
110
+ end
111
+
112
+ should "should not change the client secret" do
113
+ assert_equal "foobar", @client.secret
114
+ end
115
+
116
+ should "should change all the other properties" do
117
+ assert_equal "Rock Star", @client.display_name
118
+ end
119
+ end
120
+
121
+ context "secret mismatch" do
122
+ setup do
123
+ Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"MyApp")
124
+ end
125
+
126
+ should "raise error" do
127
+ Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"wrong", :display_name=>"MyApp")
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+
134
+
135
+ context "get_access_token" do
136
+ setup { @token = Server.get_token_for("Batman", client.id, %w{read}).token }
137
+ should "return authorization request" do
138
+ assert_equal @token, Server.get_access_token(@token).token
139
+ end
140
+
141
+ should "return nil if no client found" do
142
+ assert !Server.get_access_token("4ce2488e3321e87ac1000004")
143
+ end
144
+ end
145
+
146
+
147
+ context "get_token_for" do
148
+ setup { @token = Server.get_token_for("Batman", client.id, %w{read write}) }
149
+
150
+ should "return access token" do
151
+ assert Server::AccessToken === @token
152
+ end
153
+
154
+ should "associate token with client" do
155
+ assert_equal client.id, @token.client_id
156
+ end
157
+
158
+ should "associate token with identity" do
159
+ assert_equal "Batman", @token.identity
160
+ end
161
+
162
+ should "associate token with scope" do
163
+ assert_equal %w{read write}, @token.scope
164
+ end
165
+
166
+ should "return same token for same parameters" do
167
+ assert_equal @token.token, Server.get_token_for("Batman", client.id, %w{write read}).token
168
+ end
169
+
170
+ should "return different token for different identity" do
171
+ assert @token.token != Server.get_token_for("Superman", client.id, %w{read write}).token
172
+ end
173
+
174
+ should "return different token for different client" do
175
+ client = Server.register(:display_name=>"MyApp")
176
+ assert @token.token != Server.get_token_for("Batman", client.id, %w{read write}).token
177
+ end
178
+
179
+ should "return different token for different scope" do
180
+ assert @token.token != Server.get_token_for("Batman", client.id, %w{read}).token
181
+ end
182
+ end
183
+
184
+
185
+ context "list access tokens" do
186
+ setup do
187
+ @one = Server.get_token_for("Batman", client.id, %w{read})
188
+ @two = Server.get_token_for("Superman", client.id, %w{read})
189
+ @three = Server.get_token_for("Batman", client.id, %w{write})
190
+ end
191
+
192
+ should "return all tokens for identity" do
193
+ assert_contains Server.list_access_tokens("Batman").map(&:token), @one.token
194
+ assert_contains Server.list_access_tokens("Batman").map(&:token), @three.token
195
+ end
196
+
197
+ should "not return tokens for other identities" do
198
+ assert !Server.list_access_tokens("Batman").map(&:token).include?(@two.token)
199
+ end
200
+
201
+ end
202
+
203
+ end