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