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 +25 -7
- data/README.rdoc +18 -7
- data/VERSION +1 -1
- data/bin/oauth2-server +6 -6
- data/lib/rack/oauth2/admin/css/screen.css +8 -2
- data/lib/rack/oauth2/admin/js/application.js +44 -37
- data/lib/rack/oauth2/admin/js/sammy.oauth2.js +10 -3
- data/lib/rack/oauth2/admin/views/client.tmpl +3 -2
- data/lib/rack/oauth2/admin/views/edit.tmpl +13 -9
- data/lib/rack/oauth2/models/access_grant.rb +6 -4
- data/lib/rack/oauth2/models/access_token.rb +12 -14
- data/lib/rack/oauth2/models/auth_request.rb +10 -7
- data/lib/rack/oauth2/models/client.rb +11 -7
- data/lib/rack/oauth2/rails.rb +2 -2
- data/lib/rack/oauth2/server/admin.rb +8 -6
- data/lib/rack/oauth2/server/helper.rb +2 -2
- data/lib/rack/oauth2/server/practice.rb +1 -1
- data/lib/rack/oauth2/server/utils.rb +4 -4
- data/lib/rack/oauth2/server.rb +53 -26
- data/lib/rack/oauth2/sinatra.rb +2 -2
- data/test/admin/api_test.rb +13 -11
- data/test/oauth/access_grant_test.rb +27 -3
- data/test/oauth/access_token_test.rb +2 -25
- data/test/oauth/server_test.rb +64 -21
- data/test/rails2/log/test.log +10567 -0
- data/test/rails3/log/test.log +15977 -0
- data/test/setup.rb +2 -2
- metadata +5 -5
data/CHANGELOG
CHANGED
@@ -1,17 +1,35 @@
|
|
1
1
|
2010-11-16 version 2.0.0
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
11
|
-
|
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.
|
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
|
-
:
|
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",
|
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 :
|
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 :
|
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 :
|
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
|
-
- +
|
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.
|
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 "
|
45
|
-
|
46
|
-
client = Server.register(:display_name=>display_name, :link=>link, :redirect_uri=>redirect_uri, :
|
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, :
|
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, :
|
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 :
|
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 .
|
315
|
+
.client .scope {
|
310
316
|
float: right;
|
311
317
|
}
|
312
|
-
.client .
|
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
|
23
|
-
if ($.isArray(
|
24
|
-
|
25
|
-
|
26
|
-
return
|
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
|
29
|
-
function
|
30
|
-
if (
|
31
|
-
cb(
|
30
|
+
var commonScope;
|
31
|
+
function withCommonScope(cb) {
|
32
|
+
if (commonScope)
|
33
|
+
cb(commonScope)
|
32
34
|
else
|
33
|
-
$.getJSON(api + "/clients", function(json) { cb(
|
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
|
-
|
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
|
-
|
50
|
-
client.common =
|
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.
|
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
|
-
|
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
|
-
|
71
|
-
context.params.common =
|
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
|
-
|
109
|
-
context.partial("admin/views/edit.tmpl", { common:
|
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.
|
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
|
-
|
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
|
-
|
129
|
-
context.params.common =
|
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
|
-
//
|
10
|
-
//
|
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="
|
24
|
-
<h2>
|
29
|
+
<div class="scope">
|
30
|
+
<h2>Scope</h2>
|
25
31
|
{{each common}}
|
26
|
-
<label class="check"><input type="checkbox" name="
|
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
|
34
|
+
{{each scope}}
|
29
35
|
{{if common.indexOf(this.toString()) < 0}}
|
30
|
-
<label class="check
|
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="
|
35
|
-
<p class="hint">Space separated list of uncommon
|
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,
|
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=>
|
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
|
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
|
-
|
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,
|
19
|
-
scope = Utils.
|
20
|
-
|
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=>
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
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
|
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(
|
20
|
-
|
21
|
-
|
22
|
-
:
|
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
|
-
#
|
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,
|
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,
|
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
|
-
# # :
|
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
|
-
|
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,
|
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
|
85
|
-
attr_reader :
|
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[:
|
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
|
data/lib/rack/oauth2/rails.rb
CHANGED
@@ -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
|
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[:
|
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
|