rack-oauth2-server 2.0.0.beta3 → 2.0.0.beta4

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,17 +1,35 @@
1
1
  2010-11-16 version 2.0.0
2
2
 
3
- Major change: you are no longer able to get an access token with the scope if
4
- the client is not registered to have this scope. The global setting scopes is
5
- gone (not backward compatible). You will need to manually add scopes to your
6
- clients, e.g. in mogno console:
3
+ MAJOR CHANGE:
4
+ Keeping with OAuth 2.0 spec terminology, we'll call it scope all around. Some
5
+ places in the API that previously used "scopes" have been changed to "scope".
6
+
7
+ OTOH, the scope is not consistently stored and returned as array of names,
8
+ previous was stored as comma-separated string, and often returned as such.
9
+ Whatever you have stored with pre 2.0 will probably not work with 2.0 and
10
+ forward.
11
+
12
+ Clients now store their scope, and only those names are allowed in access
13
+ tokens. The global setting oauth.scope is no longer in use. Forget about it.
14
+ Add a scope to your clients, like this:
7
15
 
8
16
  db.oauth2.clients.update({}, { $set: { scopes: ["read", "write"] } }, true, true)
9
17
 
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.
18
+
19
+ Use Rack::OAuth2::Server token_for and access_grant to generate access tokens
20
+ and access grants, respectively. These are mighty useful if you're using the
21
+ OAuth 2.0 infrastructure, but have different ways for authorizing, e.g. using
22
+ access tokens instead of cookies.
23
+
24
+ Rack::OAuth2::Server method register to register new client applications and
25
+ update existing records. This method is idempotent, so you can use it in rake
26
+ db:seed, deploy scripts, migrations, etc.
27
+
28
+ If your authenticator accepts four arguments, it will receive, in addition to
29
+ username and password, also the client identifier and requested scopes.
12
30
 
13
31
  Web console now allows you to set/unset individual scopes for each client
14
- application.
32
+ application, and store a note on each client.
15
33
 
16
34
  Added Sammy.js OAuth 2.0 plugin.
17
35
 
data/README.rdoc CHANGED
@@ -50,7 +50,7 @@ Rack::OAuth2::Sinatra into your application. For example:
50
50
  register Rack::OAuth2::Sinatra
51
51
 
52
52
  oauth.database = Mongo::Connection.new["my_db"]
53
- oauth.scopes = %w{read write}
53
+ oauth.scope = %w{read write}
54
54
  oauth.authenticator = lambda do |username, password|
55
55
  user = User.find(username)
56
56
  user if user && user.authenticated?(password)
@@ -88,6 +88,16 @@ access tokens by passing the end-user's username/password, then you need an
88
88
  authenticator. This feature is necessary for some client applications, and
89
89
  quite handy during development/testing.
90
90
 
91
+ The authenticator is a block that receives either two or four parameters. The
92
+ first two are username and password. The other two are the client identifier
93
+ and scope. It authenticated, it returns an identity, otherwise it can return
94
+ nil or false. For example:
95
+
96
+ oauth.authenticator = lambda do |username, password|
97
+ user = User.find_by_username(username)
98
+ user if user && user.authenticated?(password)
99
+ end
100
+
91
101
 
92
102
  === Step 3: Let Users Authorize
93
103
 
@@ -294,7 +304,7 @@ Programatically, registering a new client is as simple as:
294
304
  > Rack::OAuth2::Server.register(:display_name=>"UberClient",
295
305
  :link=>"http://example.com/",
296
306
  :image_url=>"http://farm5.static.flickr.com/4122/4890273282_58f7c345f4.jpg",
297
- :scopes=>%{read write},
307
+ :scope=>%{read write},
298
308
  :redirect_uri=>"http://example.com/oauth/callback")
299
309
  > puts "Your client identifier: #{client.id}"
300
310
  > puts "Your client secret: #{client.secret}"
@@ -306,7 +316,7 @@ file. For example, your +db/seed.rb+ may contain:
306
316
  oauth2 = YAML.load_file(Rails.root + "config/oauth2.yml")
307
317
  Rack::OAuth2::Server.register(id: oauth2["client_id"], secret: oauth2["client_secret"],
308
318
  display_name: "UberClient", link: "http://example.com",
309
- redirect_uri: "http://example.com/oauth/callback", scopes: oauth2["scopes"].split)
319
+ redirect_uri: "http://example.com/oauth/callback", scope: oauth2["scope"].split)
310
320
 
311
321
  When you call +register+ with +id+ and +secret+ parameters it either registers a
312
322
  new client with these specific ID and sceret, or if a client already exists,
@@ -358,7 +368,7 @@ client ID/secret. For example, for Rails 2.3.x add this to
358
368
  config.middleware.use Rack::OAuth2::Server::Admin.mount
359
369
  Rack::OAuth2::Server::Admin.set :client_id, "4dca20453e4859cb000007"
360
370
  Rack::OAuth2::Server::Admin.set :client_secret, "981fa734e110496fcf667cbf52fbaf03"
361
- Rack::OAuth2::Server::Admin.set :scopes, %w{read write}
371
+ Rack::OAuth2::Server::Admin.set :scope, %w{read write}
362
372
  end
363
373
  end
364
374
 
@@ -369,7 +379,7 @@ For Rails 3.0.x, add this to you +config/application.rb+:
369
379
  config.after_initialize do
370
380
  Rack::OAuth2::Server::Admin.set :client_id, "4dca20453e4859cb000007"
371
381
  Rack::OAuth2::Server::Admin.set :client_secret, "981fa734e110496fcf667cbf52fbaf03"
372
- Rack::OAuth2::Server::Admin.set :scopes, %w{read write}
382
+ Rack::OAuth2::Server::Admin.set :scope, %w{read write}
373
383
  end
374
384
  end
375
385
  end
@@ -387,7 +397,7 @@ like so (e.g. in +config.ru+):
387
397
  end
388
398
  Rack::OAuth2::Server::Admin.set :client_id, "4dca20453e4859cb000007"
389
399
  Rack::OAuth2::Server::Admin.set :client_secret, "981fa734e110496fcf667cbf52fbaf03"
390
- Rack::OAuth2::Server::Admin.set :scopes, %w{read write}
400
+ Rack::OAuth2::Server::Admin.set :scope, %w{read write}
391
401
 
392
402
  Next, open your browser to http://example.com/oauth/admin, or wherever you
393
403
  mounted the Web admin.
@@ -404,7 +414,8 @@ You can set the following options:
404
414
  "http://example.com/users/#{id}")
405
415
  - +force_ssl+ -- Forces all requests to use HTTPS (true by default except in
406
416
  development mode).
407
- - +scopes+ -- Common scopes shown and added by default to new clients.
417
+ - +scope+ -- Common scope shown and added by default to new clients (array of
418
+ names, e.g. ["read", "write"]).
408
419
 
409
420
  == Web Admin API
410
421
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.beta3
1
+ 2.0.0.beta4
data/bin/oauth2-server CHANGED
@@ -41,9 +41,9 @@ when "register"
41
41
  link = $stdin.gets
42
42
  print "Redirect URI:\t\t"
43
43
  redirect_uri = $stdin.gets
44
- print "Scopes (space separated):\t\t"
45
- scopes = $stdin.gets
46
- client = Server.register(:display_name=>display_name, :link=>link, :redirect_uri=>redirect_uri, :scopes=>scopes)
44
+ print "Scope (space separated names):\t\t"
45
+ scope = $stdin.gets
46
+ client = Server.register(:display_name=>display_name, :link=>link, :redirect_uri=>redirect_uri, :scope=>scope)
47
47
  rescue
48
48
  puts "\nFailed to register client: #{$!}"
49
49
  exit -1
@@ -63,7 +63,7 @@ when "setup"
63
63
  fail "No an HTTP/S URL" unless uri.absolute? && %{http https}.include?(uri.scheme)
64
64
  fail "Path must end with /admin" unless uri.path[/\/admin$/]
65
65
  client = Server.register(:display_name=>"OAuth Console", :link=>uri.to_s, :image_url=>"#{uri.to_s}/images/oauth-2.png",
66
- :redirect_uri=>uri.to_s, :scopes=>"oauth-admin")
66
+ :redirect_uri=>uri.to_s, :scope=>"oauth-admin")
67
67
  rescue
68
68
  puts "\nFailed to register client: #{$!}"
69
69
  exit -1
@@ -131,11 +131,11 @@ when "practice"
131
131
  admin_url = "http://localhost:#{port}/oauth/admin"
132
132
  unless client = Server::Client.lookup(admin_url)
133
133
  client = Server.register(:display_name=>"Practice OAuth Console", :image_url=>"#{admin_url}/images/oauth-2.png",
134
- :link=>admin_url, :redirect_uri=>admin_url, :scopes=>"oauth-admin")
134
+ :link=>admin_url, :redirect_uri=>admin_url, :scope=>"oauth-admin")
135
135
  end
136
136
  Server::Admin.set :client_id, client.id
137
137
  Server::Admin.set :client_secret, client.secret
138
- Server::Admin.set :scopes, "nobody sudo"
138
+ Server::Admin.set :scope, "nobody sudo"
139
139
 
140
140
  print "\nFiring up the practice server.\nFor instructions, go to http://localhost:#{port}/\n\n\n"
141
141
  Thin::Server.new "127.0.0.1", port do
@@ -119,6 +119,7 @@ button:active { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(0
119
119
  float: right;
120
120
  font-size: 11pt;
121
121
  line-height: 32px;
122
+ display: none;
122
123
  }
123
124
 
124
125
  #main {
@@ -261,6 +262,7 @@ table.tokens td.scope {
261
262
  }
262
263
 
263
264
  .client .details {
265
+ margin: 0 0 2em 0;
264
266
  }
265
267
  .client .details .name {
266
268
  margin: 0;
@@ -288,6 +290,10 @@ table.tokens td.scope {
288
290
  color: #888;
289
291
  font-size: 10pt;
290
292
  }
293
+ .client .details .notes {
294
+ font-size: 11pt;
295
+ margin: 0.2em 0;
296
+ }
291
297
 
292
298
  .client.new, .client.edit {
293
299
  margin: 0;
@@ -306,10 +312,10 @@ table.tokens td.scope {
306
312
  .client .fields {
307
313
  float: left;
308
314
  }
309
- .client .scopes {
315
+ .client .scope {
310
316
  float: right;
311
317
  }
312
- .client .scopes .non-common {
318
+ .client .scope .uncommon {
313
319
  color: red;
314
320
  }
315
321
  .client hr {
@@ -15,60 +15,80 @@ Sammy("#main", function(app) {
15
15
  this.bind("oauth.denied", function(evt, error) {
16
16
  this.partial("admin/views/no_access.tmpl", { error: error.message });
17
17
  });
18
+ this.bind("oauth.connected", function() { $("#header .signout").show() });
19
+ this.bind("oauth.disconnected", function() { $("#header .signout").hide() });
18
20
 
19
21
  var api = document.location.pathname + "/api";
20
22
  // Takes array of string with scope names (typically request parameters) and
21
23
  // normalizes them into an array of scope names.
22
- function mergeScopes(scopes) {
23
- if ($.isArray(scopes))
24
- scopes = scopes.join(" ");
25
- scopes = (scopes || "").trim().split(/\s+/);
26
- return scopes.length == 1 && scopes[0] == "" ? [] : _.uniq(scopes).sort();
24
+ function mergeScope(scope) {
25
+ if ($.isArray(scope))
26
+ scope = scope.join(" ");
27
+ scope = (scope || "").trim().split(/\s+/);
28
+ return scope.length == 1 && scope[0] == "" ? [] : _.uniq(scope).sort();
27
29
  }
28
- var commonScopes;
29
- function withCommonScopes(cb) {
30
- if (commonScopes)
31
- cb(commonScopes)
30
+ var commonScope;
31
+ function withCommonScope(cb) {
32
+ if (commonScope)
33
+ cb(commonScope)
32
34
  else
33
- $.getJSON(api + "/clients", function(json) { cb(commonScopes = json.scopes); })
35
+ $.getJSON(api + "/clients", function(json) { cb(commonScope = json.scope); })
34
36
  }
35
37
 
36
38
  // View all clients
37
39
  this.get("#/", function(context) {
38
40
  context.title("All Clients");
39
41
  $.getJSON(api + "/clients", function(clients) {
40
- commonScopes = clients.scopes;
42
+ commonScope = clients.scope;
41
43
  context.partial("admin/views/clients.tmpl", { clients: clients.list, tokens: clients.tokens }).
42
44
  load(clients.history).then(function(json) { $("#fig").chart(json.data, "granted"); });
43
45
  });
44
46
  });
47
+ // View single client
48
+ this.get("#/client/:id", function(context) {
49
+ $.getJSON(api + "/client/" + context.params.id, function(client) {
50
+ context.title(client.displayName);
51
+ client.notes = (client.notes || "").split(/\n\n/);
52
+ context.partial("admin/views/client.tmpl", client).
53
+ load(client.history).then(function(json) { $("#fig").chart(json.data, "granted"); });
54
+ });
55
+ });
56
+ this.get("#/client/:id/page/:page", function(context) {
57
+ $.getJSON(api + "/client/" + context.params.id + "?page=" + context.params.page, function(client) {
58
+ context.title(client.displayName);
59
+ client.notes = client.notes.split(/\n\n/);
60
+ context.partial("admin/views/client.tmpl", client).
61
+ load(client.history).then(function(json) { $("#fig").chart(json.data, "granted"); });
62
+ });
63
+ });
45
64
  // Edit client
46
65
  this.get("#/client/:id/edit", function(context) {
47
66
  $.getJSON(api + "/client/" + context.params.id, function(client) {
48
67
  context.title(client.displayName);
49
- withCommonScopes(function(scopes) {
50
- client.common = scopes;
68
+ withCommonScope(function(scope) {
69
+ client.common = scope;
51
70
  context.partial("admin/views/edit.tmpl", client)
52
71
  })
53
72
  })
54
73
  });
55
74
  this.put("#/client/:id", function(context) {
56
- context.params.scopes = mergeScopes(context.params.scopes);
75
+ context.params.scope = mergeScope(context.params.scope);
57
76
  $.ajax({ type: "put", url: api + "/client/" + context.params.id,
58
77
  data: {
59
78
  displayName: context.params.displayName,
60
79
  link: context.params.link,
61
80
  imageUrl: context.params.imageUrl,
62
81
  redirectUri: context.params.redirectUri,
63
- scopes: context.params.scopes
82
+ notes: context.params.notes,
83
+ scope: context.params.scope
64
84
  },
65
85
  success: function(client) {
66
86
  context.redirect("#/client/" + context.params.id);
67
87
  app.trigger("notice", "Saved your changes");
68
88
  },
69
89
  error: function(xhr) {
70
- withCommonScopes(function(scopes) {
71
- context.params.common = scopes;
90
+ withCommonScope(function(scope) {
91
+ context.params.common = scope;
72
92
  context.partial("admin/views/edit.tmpl", context.params);
73
93
  });
74
94
  }
@@ -88,45 +108,32 @@ Sammy("#main", function(app) {
88
108
  this.post("#/token/:id/revoke", function(context) {
89
109
  $.post(api + "/token/" + context.params.id + "/revoke", function() { app.refresh() });
90
110
  });
91
- // View single client
92
- this.get("#/client/:id", function(context) {
93
- $.getJSON(api + "/client/" + context.params.id, function(client) {
94
- context.title(client.displayName);
95
- context.partial("admin/views/client.tmpl", client).
96
- load(client.history).then(function(json) { $("#fig").chart(json.data, "granted"); });
97
- });
98
- });
99
- this.get("#/client/:id/:page", function(context) {
100
- $.getJSON(api + "/client/" + context.params.id + "?page=" + context.params.page, function(client) {
101
- context.title(client.displayName);
102
- context.partial("admin/views/client.tmpl", client)
103
- });
104
- });
105
111
  // Create new client
106
112
  this.get("#/new", function(context) {
107
113
  context.title("Add New Client");
108
- withCommonScopes(function(scopes) {
109
- context.partial("admin/views/edit.tmpl", { common: scopes, scopes: scopes });
114
+ withCommonScope(function(scope) {
115
+ context.partial("admin/views/edit.tmpl", { common: scope, scope: scope });
110
116
  });
111
117
  });
112
118
  this.post("#/clients", function(context) {
113
119
  context.title("Add New Client");
114
- context.params.scopes = mergeScopes(context.params.scopes);
120
+ context.params.scope = mergeScope(context.params.scope);
115
121
  $.ajax({ type: "post", url: api + "/clients",
116
122
  data: {
117
123
  displayName: context.params.displayName,
118
124
  link: context.params.link,
119
125
  imageUrl: context.params.imageUrl,
120
126
  redirectUri: context.params.redirectUri,
121
- scopes: context.params.scopes
127
+ notes: context.params.notes,
128
+ scope: context.params.scope
122
129
  },
123
130
  success: function(client) {
124
131
  app.trigger("notice", "Added new client application " + client.displayName);
125
132
  context.redirect("#/");
126
133
  },
127
134
  error: function(xhr) {
128
- withCommonScopes(function(scopes) {
129
- context.params.common = scopes;
135
+ withCommonScope(function(scope) {
136
+ context.params.common = scope;
130
137
  context.partial("admin/views/edit.tmpl", context.params);
131
138
  });
132
139
  }
@@ -5,9 +5,12 @@
5
5
  // access your application's API. Requires Sammy.Session.
6
6
  //
7
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
8
+ // - oauth.connected -- Access token set and ready to use. Triggered when new
9
+ // access token acquired, of when application starts and already has access
10
+ // token.
11
+ // - oauth.disconnected -- Access token reset. Triggered by
12
+ // loseAccessToken().
13
+ // - oauth.denied -- Authorization attempt rejected.
11
14
  //
12
15
  // ### Example
13
16
  // this.use(Sammy.Storage);
@@ -27,6 +30,10 @@
27
30
  // })
28
31
  // })
29
32
  //
33
+ // // Sign in/sign out.
34
+ // this.bind("oauth.connected", function() { $("#signin").hide() });
35
+ // this.bind("oauth.disconnected", function() { $("#signin").show() });
36
+ //
30
37
  // // Handle access denied and other errors
31
38
  // this.bind("oauth.denied", function(evt, error) {
32
39
  // this.partial("admin/views/no_access.tmpl", { error: error.message });
@@ -13,6 +13,7 @@
13
13
  Created {{html $.shortdate(revoked)}}
14
14
  {{if revoked}}Revoked {{html $.shortdate(revoked)}}{{/if}}
15
15
  </div>
16
+ {{each notes}}<p class="notes">${this}</p>{{/each}}
16
17
  </div>
17
18
  <div class="metrics">
18
19
  <div id="fig"></div>
@@ -49,7 +50,7 @@
49
50
  </tbody>
50
51
  </table>
51
52
  <div class="pagination">
52
- {{if tokens.previous}}<a href="#/client/${id}/${tokens.page - 1}" rel="previous">Previous</a>{{/if}}
53
- {{if tokens.next}}<a href="#/client/${id}/${tokens.page + 1}" rel="next">Next</a>{{/if}}
53
+ {{if tokens.previous}}<a href="#/client/${id}/page/${tokens.page - 1}" rel="previous">Previous</a>{{/if}}
54
+ {{if tokens.next}}<a href="#/client/${id}/page/${tokens.page + 1}" rel="next">Next</a>{{/if}}
54
55
  </div>
55
56
  </div>
@@ -19,25 +19,29 @@
19
19
  <input type="url" name="redirectUri" value="${redirectUri}" size="70" required>
20
20
  <p class="hint">Users redirected back to this URL on successful authorization.</p>
21
21
  </label>
22
+ <label>Notes
23
+ <textarea name="notes" cols="50" rows="5">${notes}</textarea>
24
+ <p class="hint">For internal use.</p>
25
+ </label>
26
+ {{if id}}<button>Save Changes</button>
27
+ {{else}}<button>Create Client</button>{{/if}}
22
28
  </div>
23
- <div class="scopes">
24
- <h2>Scopes</h2>
29
+ <div class="scope">
30
+ <h2>Scope</h2>
25
31
  {{each common}}
26
- <label class="check"><input type="checkbox" name="scopes" value="${this}" ${scopes.indexOf(this.toString()) < 0 ? null : "checked"}>${this}</label>
32
+ <label class="check"><input type="checkbox" name="scope" value="${this}" ${scope.indexOf(this.toString()) < 0 ? null : "checked"}>${this}</label>
27
33
  {{/each}}
28
- {{each scopes}}
34
+ {{each scope}}
29
35
  {{if common.indexOf(this.toString()) < 0}}
30
- <label class="check non-common"><input type="checkbox" name="scopes" value="${this}" checked>${this}</label>
36
+ <label class="check uncommon"><input type="checkbox" name="scope" value="${this}" checked>${this}</label>
31
37
  {{/if}}
32
38
  {{/each}}
33
39
  <label>Uncommon
34
- <input type="text" name="scopes" value="" size="35">
35
- <p class="hint">Space separated list of uncommon scopes.</p>
40
+ <input type="text" name="scope" value="" size="35">
41
+ <p class="hint">Space separated list of uncommon scope.</p>
36
42
  </label>
37
43
  </div>
38
44
  <hr>
39
- {{if id}}<button>Save Changes</button>
40
- {{else}}<button>Create Client</button>{{/if}}
41
45
  </form>
42
46
  <script type="text/javascript">
43
47
  $(function() {
@@ -12,9 +12,10 @@ module Rack
12
12
  end
13
13
 
14
14
  # Create a new access grant.
15
- def create(identity, scope, client_id, redirect_uri)
15
+ def create(identity, client, scope, redirect_uri = nil)
16
+ scope = Utils.normalize_scope(scope) & client.scope # Only allowed scope
16
17
  fields = { :_id=>Server.secure_random, :identity=>identity.to_s, :scope=>scope,
17
- :client_id=>BSON::ObjectId(client_id.to_s), :redirect_uri=>redirect_uri,
18
+ :client_id=>client.id, :redirect_uri=>client.redirect_uri || redirect_uri,
18
19
  :created_at=>Time.now.utc.to_i, :granted_at=>nil, :access_token=>nil, :revoked=>nil }
19
20
  collection.insert fields
20
21
  Server.new_instance self, fields
@@ -34,7 +35,7 @@ module Rack
34
35
  attr_reader :client_id
35
36
  # Redirect URI for this grant.
36
37
  attr_reader :redirect_uri
37
- # The scope granted in this token.
38
+ # The scope requested in this grant.
38
39
  attr_reader :scope
39
40
  # Does what it says on the label.
40
41
  attr_reader :created_at
@@ -53,7 +54,8 @@ module Rack
53
54
  # InvalidGrantError.
54
55
  def authorize!
55
56
  raise InvalidGrantError if self.access_token || self.revoked
56
- access_token = AccessToken.get_token_for(identity, client_id, scope)
57
+ client = Client.find(client_id) or raise InvalidGrantError
58
+ access_token = AccessToken.get_token_for(identity, client, scope)
57
59
  self.access_token = access_token.token
58
60
  self.granted_at = Time.now.utc.to_i
59
61
  self.class.collection.update({ :_id=>code, :access_token=>nil, :revoked=>nil }, { :$set=>{ :granted_at=>granted_at, :access_token=>access_token.token } }, :safe=>true)
@@ -15,12 +15,11 @@ module Rack
15
15
  end
16
16
 
17
17
  # Get an access token (create new one if necessary).
18
- def get_token_for(identity, client_id, scope)
19
- scope = Utils.normalize_scopes(scope)
20
- client_id = BSON::ObjectId(client_id.to_s)
21
- unless token = collection.find_one({ :identity=>identity.to_s, :scope=>scope, :client_id=>client_id, :revoked=>nil })
18
+ def get_token_for(identity, client, scope)
19
+ scope = Utils.normalize_scope(scope) & client.scope # Only allowed scope
20
+ unless token = collection.find_one({ :identity=>identity.to_s, :scope=>scope, :client_id=>client.id, :revoked=>nil })
22
21
  token = { :_id=>Server.secure_random, :identity=>identity.to_s, :scope=>scope,
23
- :client_id=>client_id, :created_at=>Time.now.utc.to_i,
22
+ :client_id=>client.id, :created_at=>Time.now.utc.to_i,
24
23
  :expires_at=>nil, :revoked=>nil }
25
24
  collection.insert token
26
25
  end
@@ -50,14 +49,12 @@ module Rack
50
49
  select = {}
51
50
  if filter[:days]
52
51
  now = Time.now.utc.to_i
53
- select[:created_at] = { :$gt=>now - filter[:days] * 86400, :$lte=>now }
54
- end
55
- if filter.has_key?(:revoked)
52
+ range = { :$gt=>now - filter[:days] * 86400, :$lte=>now }
53
+ select[ filter[:revoked] ? :revoked : :created_at ] = range
54
+ elsif filter.has_key?(:revoked)
56
55
  select[:revoked] = filter[:revoked] ? { :$ne=>nil } : { :$eq=>nil }
57
56
  end
58
- if filter[:client_id]
59
- select[:client_id] = BSON::ObjectId(filter[:client_id].to_s)
60
- end
57
+ select[:client_id] = BSON::ObjectId(filter[:client_id].to_s) if filter[:client_id]
61
58
  collection.find(select).count
62
59
  end
63
60
 
@@ -68,8 +65,9 @@ module Rack
68
65
  if filter[:client_id]
69
66
  select[:client_id] = BSON::ObjectId(filter[:client_id].to_s)
70
67
  end
71
- Server::AccessToken.collection.group "function (token) { return { ts: Math.floor(token.created_at / 86400) } }",
72
- select, { :granted=>0 }, "function (token, state) { state.granted++ }"
68
+ raw = Server::AccessToken.collection.group("function (token) { return { ts: Math.floor(token.created_at / 86400) } }",
69
+ select, { :granted=>0 }, "function (token, state) { state.granted++ }")
70
+ raw.sort { |a, b| a["ts"] - b["ts"] }
73
71
  end
74
72
 
75
73
  def collection
@@ -84,7 +82,7 @@ module Rack
84
82
  attr_reader :identity
85
83
  # Client that was granted this access token.
86
84
  attr_reader :client_id
87
- # The scope granted in this token.
85
+ # The scope granted to this token.
88
86
  attr_reader :scope
89
87
  # When token was granted.
90
88
  attr_reader :created_at
@@ -16,10 +16,12 @@ module Rack
16
16
  # Create a new authorization request. This holds state, so in addition
17
17
  # to client ID and scope, we need to know the URL to redirect back to
18
18
  # and any state value to pass back in that redirect.
19
- def create(client_id, scope, redirect_uri, response_type, state)
20
- fields = { :client_id=>BSON::ObjectId(client_id.to_s), :scope=>scope, :redirect_uri=>redirect_uri, :state=>state,
21
- :response_type=>response_type, :created_at=>Time.now.utc.to_i, :grant_code=>nil,
22
- :authorized_at=>nil, :revoked=>nil }
19
+ def create(client, scope, redirect_uri, response_type, state)
20
+ scope = Utils.normalize_scope(scope) & client.scope # Only allowed scope
21
+ fields = { :client_id=>client.id, :scope=>scope, :redirect_uri=>client.redirect_uri || redirect_uri,
22
+ :response_type=>response_type, :state=>state,
23
+ :grant_code=>nil, :authorized_at=>nil,
24
+ :created_at=>Time.now.utc.to_i, :revoked=>nil }
23
25
  fields[:_id] = collection.insert(fields)
24
26
  Server.new_instance self, fields
25
27
  end
@@ -34,7 +36,7 @@ module Rack
34
36
  alias :id :_id
35
37
  # Client making this request.
36
38
  attr_reader :client_id
37
- # Scope of this request: array of names.
39
+ # scope of this request: array of names.
38
40
  attr_reader :scope
39
41
  # Redirect back to this URL.
40
42
  attr_reader :redirect_uri
@@ -57,13 +59,14 @@ module Rack
57
59
  def grant!(identity)
58
60
  raise ArgumentError, "Must supply a identity" unless identity
59
61
  return if revoked
62
+ client = Client.find(client_id) or return
60
63
  self.authorized_at = Time.now.utc.to_i
61
64
  if response_type == "code" # Requested authorization code
62
- access_grant = AccessGrant.create(identity, scope, client_id, redirect_uri)
65
+ access_grant = AccessGrant.create(identity, client, scope, redirect_uri)
63
66
  self.grant_code = access_grant.code
64
67
  self.class.collection.update({ :_id=>id, :revoked=>nil }, { :$set=>{ :grant_code=>access_grant.code, :authorized_at=>authorized_at } })
65
68
  else # Requested access token
66
- access_token = AccessToken.get_token_for(identity, client_id, scope)
69
+ access_token = AccessToken.get_token_for(identity, client, scope)
67
70
  self.access_token = access_token.token
68
71
  self.class.collection.update({ :_id=>id, :revoked=>nil, :access_token=>nil }, { :$set=>{ :access_token=>access_token.token, :authorized_at=>authorized_at } })
69
72
  end
@@ -17,7 +17,8 @@ module Rack
17
17
  # # :link -- Link to client Web site (e.g. http://uberclient.dot)
18
18
  # # :image_url -- URL of image to show alongside display name
19
19
  # # :redirect_uri -- Registered redirect URI.
20
- # # :scopes -- List of scopes the client is allowed to request.
20
+ # # :scope -- List of names the client is allowed to request.
21
+ # # :notes -- Free form text.
21
22
  #
22
23
  # This method does not validate any of these fields, in fact, you're
23
24
  # not required to set them, use them, or use them as suggested. Using
@@ -25,9 +26,10 @@ module Rack
25
26
  # how we learned that.
26
27
  def create(args)
27
28
  redirect_uri = Server::Utils.parse_redirect_uri(args[:redirect_uri]).to_s if args[:redirect_uri]
28
- scopes = Server::Utils.normalize_scopes(args[:scopes])
29
+ scope = Server::Utils.normalize_scope(args[:scope])
29
30
  fields = { :display_name=>args[:display_name], :link=>args[:link],
30
- :image_url=>args[:image_url], :redirect_uri=>redirect_uri, :scopes=>scopes,
31
+ :image_url=>args[:image_url], :redirect_uri=>redirect_uri,
32
+ :nodes=>args[:notes].to_s, :scope=>scope,
31
33
  :created_at=>Time.now.utc.to_i, :revoked=>nil }
32
34
  if args[:id] && args[:secret]
33
35
  fields[:_id], fields[:secret] = BSON::ObjectId(args[:id].to_s), args[:secret]
@@ -81,8 +83,10 @@ module Rack
81
83
  # Redirect URL. Supplied by the client if they want to restrict redirect
82
84
  # URLs (better security).
83
85
  attr_reader :redirect_uri
84
- # List of scopes the client is allowed to request.
85
- attr_reader :scopes
86
+ # List of scope the client is allowed to request.
87
+ attr_reader :scope
88
+ # Free form fields for internal use.
89
+ attr_reader :notes
86
90
  # Does what it says on the label.
87
91
  attr_reader :created_at
88
92
  # Timestamp if revoked.
@@ -99,9 +103,9 @@ module Rack
99
103
  end
100
104
 
101
105
  def update(args)
102
- fields = [:display_name, :link, :image_url].inject({}) { |h,k| v = args[k]; h[k] = v if v; h }
106
+ fields = [:display_name, :link, :image_url, :notes].inject({}) { |h,k| v = args[k]; h[k] = v if v; h }
103
107
  fields[:redirect_uri] = Server::Utils.parse_redirect_uri(args[:redirect_uri]).to_s if args[:redirect_uri]
104
- fields[:scopes] = Server::Utils.normalize_scopes(args[:scopes])
108
+ fields[:scope] = Server::Utils.normalize_scope(args[:scope])
105
109
  self.class.collection.update({ :_id=>id }, { :$set=>fields })
106
110
  self.class.find(id)
107
111
  end
@@ -13,13 +13,13 @@ module Rack
13
13
  # access scope.
14
14
  #
15
15
  # Adds oauth setting you can use to configure the module (e.g. setting
16
- # available scopes, see example).
16
+ # available scope, see example).
17
17
  #
18
18
  # @example config/environment.rb
19
19
  # require "rack/oauth2/rails"
20
20
  #
21
21
  # Rails::Initializer.run do |config|
22
- # config.oauth[:scopes] = %w{read write}
22
+ # config.oauth[:scope] = %w{read write}
23
23
  # config.oauth[:authenticator] = lambda do |username, password|
24
24
  # User.authenticated username, password
25
25
  # end