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

Sign up to get free protection for your applications and to get access to all the features.
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