rack-oauth2-server 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +10 -0
- data/Gemfile +0 -1
- data/README.rdoc +72 -1
- data/VERSION +1 -1
- data/bin/oauth2-server +107 -0
- data/lib/rack/oauth2/admin/css/screen.css +45 -9
- data/lib/rack/oauth2/admin/images/oauth-2.png +0 -0
- data/lib/rack/oauth2/admin/js/application.js +26 -19
- data/lib/rack/oauth2/admin/views/client.tmpl +3 -2
- data/lib/rack/oauth2/admin/views/clients.tmpl +14 -1
- data/lib/rack/oauth2/admin/views/index.html +10 -1
- data/lib/rack/oauth2/models.rb +2 -0
- data/lib/rack/oauth2/models/client.rb +9 -0
- data/lib/rack/oauth2/server/admin.rb +8 -1
- data/rack-oauth2-server.gemspec +1 -1
- data/test/rails/log/test.log +4828 -0
- metadata +7 -5
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
2010-11-07 version 1.3.1
|
2
|
+
|
3
|
+
Added command line tool, helps you get started and setup:
|
4
|
+
$ oauth2-server setup --db my_db
|
5
|
+
|
6
|
+
Added a touch of color to the UI and ability to delete a client.
|
7
|
+
|
8
|
+
You can not sign out of the Web console.
|
9
|
+
|
10
|
+
|
1
11
|
2010-11-07 version 1.3.0
|
2
12
|
|
3
13
|
Added OAuth authorization console.
|
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -281,7 +281,13 @@ Clients can also register a redirect URL. This is optional but highly
|
|
281
281
|
recommended for better security, preventing other applications from hijackin
|
282
282
|
the client's ID/secret.
|
283
283
|
|
284
|
-
|
284
|
+
You can register clients using the command line tool +oauth2-server+:
|
285
|
+
|
286
|
+
$ oauth2-server register --db my_db
|
287
|
+
|
288
|
+
Or you can register clients using the Web-based OAuth console, see below.
|
289
|
+
|
290
|
+
Programatically, registering a new client is as simple as:
|
285
291
|
|
286
292
|
$ ./script/console
|
287
293
|
Loading development environment (Rails 2.3.8)
|
@@ -332,6 +338,10 @@ when making POST request, as a form field:
|
|
332
338
|
$ curl -i http://localhost:3000/api/read?oauth_token=e57807eb99f8c29f60a27a75a80fec6e
|
333
339
|
$ curl -i http://localhost:3000/api/update -F name=Superman -F oauth_token=e57807eb99f8c29f60a27a75a80fec6e
|
334
340
|
|
341
|
+
You'll need to set the option +param_authentication+ to true. Watch out, since
|
342
|
+
this query parameter could conflict with OAuth 1.0 authorization responses that
|
343
|
+
also use +oauth_token+ for a different purpose.
|
344
|
+
|
335
345
|
Here's a neat trick. You can create a +.curlrc+ file and load it using the +-K+ option:
|
336
346
|
|
337
347
|
$ cat .curlrc
|
@@ -344,6 +354,67 @@ server you +curl+. Useful for development, testing, just don't use it with any
|
|
344
354
|
production access tokens.
|
345
355
|
|
346
356
|
|
357
|
+
== OAuth Web Console
|
358
|
+
|
359
|
+
We haz it, and it's pretty rad:
|
360
|
+
|
361
|
+
http://github.com/downloads/flowtown/rack-oauth2-server/OAuth%20Console%20-%20All%20Clients.png
|
362
|
+
|
363
|
+
To get the console running, you'll need to do the following. First, you'll need
|
364
|
+
to register a new client application that can access the OAuth console, with a
|
365
|
+
redirect_uri that points to where you plan the Web console to live. This URL
|
366
|
+
must end with "/admin", for example, "http://example.com/oauth/admin".
|
367
|
+
|
368
|
+
The easiest way to do this is to run the +oauth2-sever+ command line tool:
|
369
|
+
|
370
|
+
$ oauth2-server setup --db my_db
|
371
|
+
|
372
|
+
Next, in your application, make sure to ONLY AUTHORIZE ADMINISTRATORS to access
|
373
|
+
the console, by granting them access to the +oauth-admin+ scope. For example:
|
374
|
+
|
375
|
+
def authorize
|
376
|
+
# Only admins allowed to authorize the scope oauth-admin
|
377
|
+
if oauth.scope.include?("oauth-admin") && !current_user.admin?
|
378
|
+
oauth.deny! oauth.authorization
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
Make sure you do that, or you'll allow anyone access to the OAuth Web console.
|
383
|
+
|
384
|
+
Next, mount the OAuth Web console as part of your application, and feed it the
|
385
|
+
client ID/secret. For example, for Rails add this to +config/environment.rb+:
|
386
|
+
|
387
|
+
Rails::Initializer.run do |config|
|
388
|
+
. . .
|
389
|
+
config.middleware.use Rack::OAuth2::Server::Admin.mount
|
390
|
+
Rack::OAuth2::Server::Admin.set :client_id, "4dca20453e4859cb000007"
|
391
|
+
Rack::OAuth2::Server::Admin.set :client_secret, "981fa734e110496fcf667cbf52fbaf03"
|
392
|
+
end
|
393
|
+
|
394
|
+
For Sinatra, Padrino and other Rack-based applications, you'll want to mount
|
395
|
+
like so (e.g. in +config.ru+):
|
396
|
+
|
397
|
+
Rack::Builder.new do
|
398
|
+
map("/oauth/admin") { run Rack::OAuth2::Server::Admin }
|
399
|
+
map("/") { run MyApp }
|
400
|
+
end
|
401
|
+
Rack::OAuth2::Server::Admin.set :client_id, "4dca20453e4859cb000007"
|
402
|
+
Rack::OAuth2::Server::Admin.set :client_secret, "981fa734e110496fcf667cbf52fbaf03"
|
403
|
+
|
404
|
+
Next, open your browser to http://example.com/oauth/admin, or wherever you
|
405
|
+
mounted the console.
|
406
|
+
|
407
|
+
The OAuth Web console is a single-page client application that operates by
|
408
|
+
accessing the OAuth API. The API is mounted at /oauth/admin/api (basically /api
|
409
|
+
relative to the console), you can access it yourself if you have an access
|
410
|
+
token with the scope +oauth-admin+.
|
411
|
+
|
412
|
+
To get the access token, the console first redirects you to the path
|
413
|
+
+/oauth/authorize+, which it expects to be the OAuth 2.0 authorization
|
414
|
+
endpoint. If you need to use a different endpoint, simply set the :authorize
|
415
|
+
option to that URL.
|
416
|
+
|
417
|
+
|
347
418
|
== Mandatory ASCII Diagram
|
348
419
|
|
349
420
|
This is briefly what the authorization flow looks like, how the workload is
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.3.
|
1
|
+
1.3.1
|
data/bin/oauth2-server
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
require "rack/oauth2/models"
|
4
|
+
require "uri"
|
5
|
+
include Rack::OAuth2
|
6
|
+
|
7
|
+
|
8
|
+
if (i = ARGV.index("--db")) && ARGV[i+1]
|
9
|
+
url = ARGV[i + 1]
|
10
|
+
uri = URI.parse(url)
|
11
|
+
uri = URI.parse("mongo://#{url}") if uri.opaque
|
12
|
+
db = uri.path.sub(/^\//, "")
|
13
|
+
conn = Mongo::Connection.new(uri.host, uri.port)
|
14
|
+
coon.add_auth db, uri.user, uri.password if uri.user
|
15
|
+
Server.database = conn[db]
|
16
|
+
ARGV[i,2] = []
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
case ARGV[0]
|
21
|
+
when "list"
|
22
|
+
fail "No database. Use the --db option to tell us which database to use" unless Server.database
|
23
|
+
Server::Client.all.each do |client|
|
24
|
+
next if client.revoked
|
25
|
+
print "%-30s\t%s\n" % [client.display_name, client.link]
|
26
|
+
print " ID %s\tSecret %s\n" % [client.id, client.secret]
|
27
|
+
print "\n"
|
28
|
+
end
|
29
|
+
when "register"
|
30
|
+
fail "No database. Use the --db option to tell us which database to use" unless Server.database
|
31
|
+
begin
|
32
|
+
print "Application name:\t"
|
33
|
+
display_name = $stdin.gets
|
34
|
+
print "Application URL:\t"
|
35
|
+
link = $stdin.gets
|
36
|
+
print "Redirect URI:\t\t"
|
37
|
+
redirect_uri = $stdin.gets
|
38
|
+
client = Server::Client.create(:display_name=>display_name, :link=>link, :redirect_uri=>redirect_uri)
|
39
|
+
rescue
|
40
|
+
puts "\nFailed to register client: #{$1}"
|
41
|
+
exit -1
|
42
|
+
end
|
43
|
+
puts "Registered #{client.display_name}"
|
44
|
+
puts "ID\t#{client.id}"
|
45
|
+
puts "Secret\t#{client.secret}"
|
46
|
+
when "setup"
|
47
|
+
fail "No database. Use the --db option to tell us which database to use" unless Server.database
|
48
|
+
puts "Where would you mount the Web console? This is a URL that must end with /admin,"
|
49
|
+
puts "for example, http://example.com/oauth/admin"
|
50
|
+
uri = URI.parse($stdin.gets)
|
51
|
+
begin
|
52
|
+
uri.normalize!
|
53
|
+
fail "No an HTTP/S URL" unless uri.absolute? && %{http https}.include?(uri.scheme)
|
54
|
+
fail "Path must end with /admin" unless uri.path[/\/admin$/]
|
55
|
+
client = Server::Client.create(:display_name=>"OAuth Console", :link=>uri.to_s,
|
56
|
+
:image_url=>"#{uri.to_s}/images/oauth-2.png", :redirect_uri=>uri.to_s)
|
57
|
+
rescue
|
58
|
+
puts "\nFailed to register client: #{$!}"
|
59
|
+
exit -1
|
60
|
+
end
|
61
|
+
print <<-TEXT
|
62
|
+
Make sure you ONLY authorize administrators to use the oauth-admin scope.
|
63
|
+
For example:
|
64
|
+
|
65
|
+
def authorize
|
66
|
+
# Only admins allowed to authorize the scope oauth-admin
|
67
|
+
if oauth.scope.include?("oauth-admin") && !current_user.admin?
|
68
|
+
oauth.deny! oauth.authorization
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Rails 2.x, add the following to config/environment.rb:
|
73
|
+
|
74
|
+
config.middleware.use Rack::OAuth2::Server::Admin.mount "#{uri.path}"
|
75
|
+
Rack::OAuth2::Server::Admin.set :client_id, "#{client.id}"
|
76
|
+
Rack::OAuth2::Server::Admin.set :client_secret, "#{client.secret}"
|
77
|
+
|
78
|
+
Sinatra, Padrino and other Rack applications, mount the console:
|
79
|
+
|
80
|
+
Rack::Builder.new do
|
81
|
+
map("#{uri.path}") { run Rack::OAuth2::Server::Admin }
|
82
|
+
map("/") { run MyApp }
|
83
|
+
end
|
84
|
+
Rack::OAuth2::Server::Admin.set :client_id, "#{client.id}"
|
85
|
+
Rack::OAuth2::Server::Admin.set :client_secret, "#{client.secret}"
|
86
|
+
|
87
|
+
The console will authorize access by redirecting to
|
88
|
+
https://#{uri.host}/oauth/authorize
|
89
|
+
|
90
|
+
If this is not your OAuth 2.0 authorization endpoint, you can change it by
|
91
|
+
setting the :authorize option.
|
92
|
+
TEXT
|
93
|
+
else
|
94
|
+
print <<-TEXT
|
95
|
+
Usage: oauth2-server [options] COMMAND [args]
|
96
|
+
|
97
|
+
Commands:
|
98
|
+
list Lists all active clients
|
99
|
+
register Register a new client application
|
100
|
+
setup Create new admin account and help you
|
101
|
+
setup the OAuth Web console
|
102
|
+
|
103
|
+
Options:
|
104
|
+
--db database Database name or connection URL
|
105
|
+
TEXT
|
106
|
+
exit -1
|
107
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
html {
|
2
|
+
background: #eee;
|
3
|
+
}
|
1
4
|
body {
|
2
5
|
margin: 0;
|
6
|
+
padding: 0;
|
3
7
|
width: 100%;
|
4
8
|
font: 12pt "Helvetica", "Lucida Sans", "Verdana";
|
5
9
|
}
|
@@ -75,20 +79,45 @@ button:active { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(0
|
|
75
79
|
}
|
76
80
|
|
77
81
|
#header {
|
78
|
-
margin: 0
|
82
|
+
margin: 0;
|
83
|
+
padding: 2em;
|
84
|
+
background: #fff;
|
85
|
+
background: -webkit-gradient(linear, left top, left bottom, from(#ccf), to(#fff));
|
86
|
+
background: -moz-linear-gradient(top, #ccf, #fff);
|
79
87
|
}
|
80
88
|
#header .title {
|
81
|
-
font-size:
|
89
|
+
font-size: 18pt;
|
82
90
|
font-weight: bold;
|
83
|
-
color: #000;
|
84
|
-
text-decoration: none;
|
85
|
-
margin: 0.6em 0;
|
86
91
|
display: block;
|
87
92
|
text-align: right;
|
93
|
+
line-height: 32px;
|
94
|
+
}
|
95
|
+
#header .title a {
|
96
|
+
color: #000;
|
97
|
+
text-decoration: none;
|
98
|
+
}
|
99
|
+
#header .title img {
|
100
|
+
width: 32px;
|
101
|
+
height: 32px;
|
102
|
+
vertical-align: bottom;
|
103
|
+
}
|
104
|
+
#header .signout {
|
105
|
+
float: right;
|
106
|
+
font-size: 11pt;
|
88
107
|
}
|
89
108
|
|
90
109
|
#main {
|
91
|
-
|
110
|
+
padding: 0 2em 4em 2em;
|
111
|
+
background: #fff;
|
112
|
+
}
|
113
|
+
#footer {
|
114
|
+
background: #eee;
|
115
|
+
background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#eee));
|
116
|
+
background: -moz-linear-gradient(top, #fff, #eee);
|
117
|
+
color: #666;
|
118
|
+
border-top: 1px solid transparent;
|
119
|
+
font-size: 90%;
|
120
|
+
padding: 0 2em 2em 2em;
|
92
121
|
}
|
93
122
|
|
94
123
|
table {
|
@@ -97,6 +126,7 @@ table {
|
|
97
126
|
empty-cells: show;
|
98
127
|
border-collapse: separate;
|
99
128
|
border-spacing: 0px;
|
129
|
+
margin-top: 2em;
|
100
130
|
}
|
101
131
|
table th {
|
102
132
|
text-align: left;
|
@@ -177,7 +207,7 @@ table.tokens td.scope {
|
|
177
207
|
|
178
208
|
.badges {
|
179
209
|
list-style: none;
|
180
|
-
margin:
|
210
|
+
margin: 0;
|
181
211
|
padding: 0;
|
182
212
|
text-align: right;
|
183
213
|
width: 100%;
|
@@ -211,13 +241,19 @@ table.tokens td.scope {
|
|
211
241
|
height: 24px;
|
212
242
|
vertical-align: bottom;
|
213
243
|
}
|
214
|
-
.client .details a
|
215
|
-
margin: 0 0.3em 0
|
244
|
+
.client .details a {
|
245
|
+
margin: 0 0.3em 0 0;
|
216
246
|
}
|
217
247
|
.client .details .meta {
|
218
248
|
color: #888;
|
219
249
|
font-size: 10pt;
|
220
250
|
}
|
251
|
+
|
252
|
+
.client.new, .client.edit {
|
253
|
+
margin: 0;
|
254
|
+
padding: 1em;
|
255
|
+
border: 1px solid #eee;
|
256
|
+
}
|
221
257
|
.client.new>#image, .client.edit>#image {
|
222
258
|
float: left;
|
223
259
|
margin: 0 12px 0 0;
|
Binary file
|
@@ -64,6 +64,20 @@ Sammy("#main", function(app) {
|
|
64
64
|
}
|
65
65
|
})
|
66
66
|
});
|
67
|
+
// Delete/revoke client
|
68
|
+
this.del("#/client/:id", function(context) {
|
69
|
+
$.ajax({ type: "post", url: api + "/client/" + context.params.id,
|
70
|
+
data: { _method: "delete" },
|
71
|
+
success: function() { context.redirect("#/") }
|
72
|
+
});
|
73
|
+
});
|
74
|
+
this.post("#/client/:id/revoke", function(context) {
|
75
|
+
$.post(api + "/client/" + context.params.id + "/revoke", function() { app.refresh() });
|
76
|
+
});
|
77
|
+
// Revoke token
|
78
|
+
this.post("#/token/:id/revoke", function(context) {
|
79
|
+
$.post(api + "/token/" + context.params.id + "/revoke", function() { app.refresh() });
|
80
|
+
});
|
67
81
|
// View single client
|
68
82
|
this.get("#/client/:id", function(context) {
|
69
83
|
$.getJSON(api + "/client/" + context.params.id, function(client) {
|
@@ -101,29 +115,22 @@ Sammy("#main", function(app) {
|
|
101
115
|
}
|
102
116
|
});
|
103
117
|
});
|
118
|
+
// Signout
|
119
|
+
this.get("#/signout", function(context) {
|
120
|
+
app.session("oauth.token", null);
|
121
|
+
context.redirect(document.location.protocol + "//" + document.location.host);
|
122
|
+
});
|
104
123
|
|
105
|
-
//
|
106
|
-
$("a[data-method
|
124
|
+
// Links that use forms for various methods (i.e. post, delete).
|
125
|
+
$("a[data-method]").live("click", function(evt) {
|
107
126
|
evt.preventDefault();
|
108
127
|
var link = $(this);
|
109
128
|
if (link.attr("data-confirm") && !confirm(link.attr("data-confirm")))
|
110
|
-
return;
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
});
|
116
|
-
// Link to reveal/hide client ID/secret
|
117
|
-
$("td.secrets a[rel=toggle]").live("click", function(evt) {
|
118
|
-
evt.preventDefault();
|
119
|
-
var dl = $(this).next("dl");
|
120
|
-
if (dl.is(":visible")) {
|
121
|
-
$(this).html("Reveal");
|
122
|
-
dl.hide();
|
123
|
-
} else {
|
124
|
-
$(this).html("Hide");
|
125
|
-
dl.show();
|
126
|
-
}
|
129
|
+
return fasle;
|
130
|
+
var method = link.attr("data-method") || "get",
|
131
|
+
form = $("<form>", { style: "display:none", method: method, action: link.attr("href") });
|
132
|
+
app.$element().append(form);
|
133
|
+
form.submit();
|
127
134
|
});
|
128
135
|
// Error/notice at top of screen
|
129
136
|
var noticeTimeout;
|
@@ -3,8 +3,9 @@
|
|
3
3
|
<a href="${link}" class="name">{{if imageUrl}}<img src="${imageUrl}">{{/if}} ${displayName}</a>
|
4
4
|
<a href="#/client/${id}/edit" rel="edit">Edit</a>
|
5
5
|
{{if !revoked}}
|
6
|
-
<a href="
|
6
|
+
<a href="#/client/${id}/revoke" data-method="post" data-confirm="There is no undo. Are you really really sure?" rel="revoke">Revoke</a>
|
7
7
|
{{/if}}
|
8
|
+
<a href="#/client/${id}" data-method="delete" data-confirm="There is no undo. Are you really really sure?" rel="delete">Delete</a>
|
8
9
|
<div class="meta">
|
9
10
|
Created {{html $.shortdate(revoked)}}
|
10
11
|
{{if revoked}}Revoked {{html $.shortdate(revoked)}}{{/if}}
|
@@ -34,7 +35,7 @@
|
|
34
35
|
{{if revoked}}
|
35
36
|
{{html $.shortdate(revoked)}}
|
36
37
|
{{else}}
|
37
|
-
<a href="
|
38
|
+
<a href="#/token/${token}/revoke" data-method="post" data-confirm="Are you sure?" rel="revoke">Revoke</a>
|
38
39
|
{{/if}}
|
39
40
|
</td>
|
40
41
|
</tr>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
<td class="name">
|
18
18
|
<a href="#/client/${id}">
|
19
19
|
{{if imageUrl}}<img src="${imageUrl}">{{/if}}
|
20
|
-
${displayName}
|
20
|
+
${displayName.trim() == "" ? "untitled" : displayName}
|
21
21
|
</a>
|
22
22
|
</td>
|
23
23
|
<td class="secrets">
|
@@ -34,3 +34,16 @@
|
|
34
34
|
{{/each}}
|
35
35
|
</table>
|
36
36
|
</div>
|
37
|
+
<script type="text/javascript">
|
38
|
+
$("td.secrets a[rel=toggle]").click(function(evt) {
|
39
|
+
evt.preventDefault();
|
40
|
+
var dl = $(this).next("dl");
|
41
|
+
if (dl.is(":visible")) {
|
42
|
+
$(this).html("Reveal");
|
43
|
+
dl.hide();
|
44
|
+
} else {
|
45
|
+
$(this).html("Hide");
|
46
|
+
dl.show();
|
47
|
+
}
|
48
|
+
});
|
49
|
+
</script>
|
@@ -12,11 +12,20 @@
|
|
12
12
|
<script src="admin/js/sammy.title.js" type="text/javascript"></script>
|
13
13
|
<script src="admin/js/underscore.js" type="text/javascript"></script>
|
14
14
|
<script src="admin/js/application.js" type="text/javascript"></script>
|
15
|
+
<link rel="shortcut icon" href="admin/images/oauth-2.png">
|
15
16
|
</head>
|
16
17
|
<body>
|
17
18
|
<div id="notice" style="display:none"></div>
|
18
|
-
<div id="header"
|
19
|
+
<div id="header">
|
20
|
+
<div class="title">
|
21
|
+
<a href="#/"><img src="admin/images/oauth-2.png"> OAuth Console</a>
|
22
|
+
</div>
|
23
|
+
<a href="#/signout" class="signout">Sign out</a>
|
24
|
+
</div>
|
19
25
|
<div id="main"></div>
|
26
|
+
<div id="footer">
|
27
|
+
<p>Powered by <a href="http://github.com/flowtown/rack-oauth2-server">Rack::OAuth2::Server</a></p>
|
28
|
+
</div>
|
20
29
|
<script>
|
21
30
|
var loading = new Image();
|
22
31
|
loading.src = "admin/images/loading.gif";
|