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

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 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