rack-oauth2-server 1.2.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +14 -0
- data/Gemfile +3 -0
- data/README.rdoc +26 -7
- data/Rakefile +1 -1
- data/VERSION +1 -0
- data/lib/rack/oauth2/admin/css/screen.css +233 -0
- data/lib/rack/oauth2/admin/images/loading.gif +0 -0
- data/lib/rack/oauth2/admin/js/application.js +154 -0
- data/lib/rack/oauth2/admin/js/jquery.js +166 -0
- data/lib/rack/oauth2/admin/js/jquery.tmpl.js +414 -0
- data/lib/rack/oauth2/admin/js/sammy.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.json.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.storage.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.title.js +5 -0
- data/lib/rack/oauth2/admin/js/sammy.tmpl.js +5 -0
- data/lib/rack/oauth2/admin/js/underscore.js +722 -0
- data/lib/rack/oauth2/admin/views/client.tmpl +48 -0
- data/lib/rack/oauth2/admin/views/clients.tmpl +36 -0
- data/lib/rack/oauth2/admin/views/edit.tmpl +57 -0
- data/lib/rack/oauth2/admin/views/index.html +26 -0
- data/lib/rack/oauth2/models/access_grant.rb +6 -4
- data/lib/rack/oauth2/models/access_token.rb +36 -4
- data/lib/rack/oauth2/models/auth_request.rb +4 -3
- data/lib/rack/oauth2/models/client.rb +15 -2
- data/lib/rack/oauth2/server.rb +71 -58
- data/lib/rack/oauth2/server/admin.rb +216 -0
- data/lib/rack/oauth2/server/helper.rb +4 -4
- data/lib/rack/oauth2/sinatra.rb +2 -2
- data/rack-oauth2-server.gemspec +2 -3
- data/test/admin/api_test.rb +196 -0
- data/test/admin_test_.rb +49 -0
- data/test/{access_grant_test.rb → oauth/access_grant_test.rb} +1 -1
- data/test/{access_token_test.rb → oauth/access_token_test.rb} +83 -12
- data/test/{authorization_test.rb → oauth/authorization_test.rb} +1 -1
- data/test/rails/config/environment.rb +2 -0
- data/test/rails/log/test.log +72938 -0
- data/test/setup.rb +17 -1
- data/test/sinatra/my_app.rb +1 -1
- metadata +27 -9
- data/lib/rack/oauth2/server/version.rb +0 -9
@@ -0,0 +1,48 @@
|
|
1
|
+
<div class="client">
|
2
|
+
<div class="details">
|
3
|
+
<a href="${link}" class="name">{{if imageUrl}}<img src="${imageUrl}">{{/if}} ${displayName}</a>
|
4
|
+
<a href="#/client/${id}/edit" rel="edit">Edit</a>
|
5
|
+
{{if !revoked}}
|
6
|
+
<a href="${revoke}" data-method="post" data-confirm="There is no undo. Are you really really sure?" rel="revoke">Revoke</a>
|
7
|
+
{{/if}}
|
8
|
+
<div class="meta">
|
9
|
+
Created {{html $.shortdate(revoked)}}
|
10
|
+
{{if revoked}}Revoked {{html $.shortdate(revoked)}}{{/if}}
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<ul class="badges">
|
14
|
+
<li title="Access tokens granted, lifetime total"><big>${$.thousands(tokens.total)}</big><small>Granted</small></li>
|
15
|
+
<li title="Access tokens granted, last 7 days"><big>${$.thousands(tokens.week)}</big><small>This Week</small></li>
|
16
|
+
<li title="Access tokens revoked, last 7 days"><big>${$.thousands(tokens.revoked)}</big><small>Revoked (Week)</small></li>
|
17
|
+
</ul>
|
18
|
+
<table class="tokens">
|
19
|
+
<thead>
|
20
|
+
<th>Token</th>
|
21
|
+
<th>Identity</th>
|
22
|
+
<th>Scope</th>
|
23
|
+
<th>Created</th>
|
24
|
+
<th>Revoked</th>
|
25
|
+
</thead>
|
26
|
+
<tbody>
|
27
|
+
{{each tokens.list}}
|
28
|
+
<tr>
|
29
|
+
<td class="token">${token}</td>
|
30
|
+
<td class="identity">${identity}</td>
|
31
|
+
<td class="scope">${scope}</td>
|
32
|
+
<td class="created">{{html $.shortdate(created)}}</td>
|
33
|
+
<td class="revoke">
|
34
|
+
{{if revoked}}
|
35
|
+
{{html $.shortdate(revoked)}}
|
36
|
+
{{else}}
|
37
|
+
<a href="${revoke}" data-method="post" data-confirm="Are you sure?" rel="revoke">Revoke</a>
|
38
|
+
{{/if}}
|
39
|
+
</td>
|
40
|
+
</tr>
|
41
|
+
{{/each}}
|
42
|
+
</tbody>
|
43
|
+
</table>
|
44
|
+
<div class="pagination">
|
45
|
+
{{if tokens.previous}}<a href="#/client/${id}/${tokens.page - 1}" rel="previous">Previous</a>{{/if}}
|
46
|
+
{{if tokens.next}}<a href="#/client/${id}/${tokens.page + 1}" rel="next">Next</a>{{/if}}
|
47
|
+
</div>
|
48
|
+
</div>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<div class="client">
|
2
|
+
<a href="#/new" style="float:left">Add New Client</a>
|
3
|
+
<ul class="badges">
|
4
|
+
<li title="Access tokens granted, lifetime total"><big>${$.thousands(tokens.total)}</big><small>Granted</small></li>
|
5
|
+
<li title="Access tokens granted, last 7 days"><big>${$.thousands(tokens.week)}</big><small>This Week</small></li>
|
6
|
+
<li title="Access tokens revoked, last 7 days"><big>${$.thousands(tokens.revoked)}</big><small>Revoked (Week)</small></li>
|
7
|
+
</ul>
|
8
|
+
<table class="clients">
|
9
|
+
<thead>
|
10
|
+
<th>Application</th>
|
11
|
+
<th>ID/Secret</th>
|
12
|
+
<th>Created</th>
|
13
|
+
<th>Revoked</th>
|
14
|
+
</thead>
|
15
|
+
{{each clients}}
|
16
|
+
<tr class="${revoked ? "revoked" : "active"}">
|
17
|
+
<td class="name">
|
18
|
+
<a href="#/client/${id}">
|
19
|
+
{{if imageUrl}}<img src="${imageUrl}">{{/if}}
|
20
|
+
${displayName}
|
21
|
+
</a>
|
22
|
+
</td>
|
23
|
+
<td class="secrets">
|
24
|
+
<a href="" rel="toggle">Reveal</a>
|
25
|
+
<dl>
|
26
|
+
<dt>ID</dt><dd>${id}</dd>
|
27
|
+
<dt>Secret</dt><dd>${secret}</dd>
|
28
|
+
<dt>Redirect</dt><dd>${redirectUri}</dd>
|
29
|
+
</dl>
|
30
|
+
</td>
|
31
|
+
<td class="created">{{html $.shortdate(created)}}</td>
|
32
|
+
<td class="revoke">{{if revoked}}{{html $.shortdate(revoked)}}{{/if}}</td>
|
33
|
+
</tr>
|
34
|
+
{{/each}}
|
35
|
+
</table>
|
36
|
+
</div>
|
@@ -0,0 +1,57 @@
|
|
1
|
+
{{if id}}<form action="#/client/${id}" method="put" class="client edit">
|
2
|
+
{{else}}<form action="#/clients" method="post" class="client new">{{/if}}
|
3
|
+
<img id="image">
|
4
|
+
<label>Display Name
|
5
|
+
<input type="text" name="displayName" value="${displayName}" size="30" autofocus>
|
6
|
+
<p class="hint">This is the application name that users see when asked to authorize.</p>
|
7
|
+
</label>
|
8
|
+
<label>Site URL
|
9
|
+
<input type="text" name="link" value="${link}" size="30">
|
10
|
+
<p class="hint">This is a link to the application's site.</p>
|
11
|
+
</label>
|
12
|
+
<label>Image URL
|
13
|
+
<input type="text" name="imageUrl" value="${imageUrl}" size="30">
|
14
|
+
<p class="hint">This is a link to the application's icon (48x48).</p>
|
15
|
+
</label>
|
16
|
+
<label>Redirect URI
|
17
|
+
<input type="text" name="redirectUri" value="${redirectUri}" size="30">
|
18
|
+
<p class="hint">Users redirected back to this URL on successful authorization.</p>
|
19
|
+
</label>
|
20
|
+
{{if id}}<button>Save Changes</button>
|
21
|
+
{{else}}<button>Create Client</button>{{/if}}
|
22
|
+
</form>
|
23
|
+
<script type="text/javascript">
|
24
|
+
$(function() {
|
25
|
+
var image = $("#image");
|
26
|
+
image.load(function() {
|
27
|
+
image.show().removeClass("loading");
|
28
|
+
}).error(function() {
|
29
|
+
if (image.attr("src"))
|
30
|
+
image.removeClass("loading");
|
31
|
+
});
|
32
|
+
|
33
|
+
var imageUrl = $("input[name=imageUrl]");
|
34
|
+
imageUrl.change(function() {
|
35
|
+
var url = $(this).val().trim();
|
36
|
+
if (url == "") {
|
37
|
+
image.hide();
|
38
|
+
} else {
|
39
|
+
image.attr("src", "admin/images/loading.gif").show().addClass("loading");
|
40
|
+
setTimeout(function() { image.attr("src", url); }, 10);
|
41
|
+
}
|
42
|
+
}).trigger("change");
|
43
|
+
|
44
|
+
$("input[name=link]").change(function() {
|
45
|
+
if (imageUrl.val().trim() == "") {
|
46
|
+
$("#image").show().addClass("loading").attr("src", null);
|
47
|
+
var image = new Image();
|
48
|
+
image.src = $(this).val().trim().replace(/^(https?:\/\/)(.+?)(\/.*|$)/, "$1$2/favicon.ico");
|
49
|
+
image.onload = function() {
|
50
|
+
if (imageUrl.val().trim() == "") {
|
51
|
+
imageUrl.val(image.src).trigger("change");
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
});
|
56
|
+
})
|
57
|
+
</script>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>OAuth Console</title>
|
5
|
+
<link href="admin/css/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
|
6
|
+
<script src="admin/js/jquery.js" type="text/javascript"></script>
|
7
|
+
<script src="admin/js/jquery.tmpl.js" type="text/javascript"></script>
|
8
|
+
<script src="admin/js/sammy.js" type="text/javascript"></script>
|
9
|
+
<script src="admin/js/sammy.tmpl.js" type="text/javascript"></script>
|
10
|
+
<script src="admin/js/sammy.json.js" type="text/javascript"></script>
|
11
|
+
<script src="admin/js/sammy.storage.js" type="text/javascript"></script>
|
12
|
+
<script src="admin/js/sammy.title.js" type="text/javascript"></script>
|
13
|
+
<script src="admin/js/underscore.js" type="text/javascript"></script>
|
14
|
+
<script src="admin/js/application.js" type="text/javascript"></script>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<div id="notice" style="display:none"></div>
|
18
|
+
<div id="header"><a href="#/" class="title">OAuth Console</a></div>
|
19
|
+
<div id="main"></div>
|
20
|
+
<script>
|
21
|
+
var loading = new Image();
|
22
|
+
loading.src = "admin/images/loading.gif";
|
23
|
+
$(function() { Sammy("#main").run("#/"); });
|
24
|
+
</script>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -13,8 +13,9 @@ module Rack
|
|
13
13
|
|
14
14
|
# Create a new access grant.
|
15
15
|
def create(identity, scope, client_id, redirect_uri)
|
16
|
-
fields = { :_id=>Server.secure_random, :identity=>identity.to_s, :scope=>scope,
|
17
|
-
:
|
16
|
+
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
|
+
:created_at=>Time.now.utc.to_i, :granted_at=>nil, :access_token=>nil, :revoked=>nil }
|
18
19
|
collection.insert fields
|
19
20
|
Server.new_instance self, fields
|
20
21
|
end
|
@@ -54,7 +55,7 @@ module Rack
|
|
54
55
|
raise InvalidGrantError if self.access_token || self.revoked
|
55
56
|
access_token = AccessToken.get_token_for(identity, scope, client_id)
|
56
57
|
self.access_token = access_token.token
|
57
|
-
self.granted_at = Time.now.utc
|
58
|
+
self.granted_at = Time.now.utc.to_i
|
58
59
|
self.class.collection.update({ :_id=>code, :access_token=>nil, :revoked=>nil }, { :$set=>{ :granted_at=>granted_at, :access_token=>access_token.token } }, :safe=>true)
|
59
60
|
reload = self.class.collection.find_one({ :_id=>code, :revoked=>nil }, { :fields=>%w{access_token} })
|
60
61
|
raise InvalidGrantError unless reload && reload["access_token"] == access_token.token
|
@@ -62,7 +63,8 @@ module Rack
|
|
62
63
|
end
|
63
64
|
|
64
65
|
def revoke!
|
65
|
-
self.
|
66
|
+
self.revoked = Time.now.utc.to_i
|
67
|
+
self.class.collection.update({ :_id=>code, :revoked=>nil }, { :$set=>{ :revoked=>revoked } })
|
66
68
|
end
|
67
69
|
|
68
70
|
Server.create_indexes do
|
@@ -8,6 +8,7 @@ module Rack
|
|
8
8
|
# and scope. It may be revoked, or expire after a certain period.
|
9
9
|
class AccessToken
|
10
10
|
class << self
|
11
|
+
|
11
12
|
# Find AccessToken from token. Does not return revoked tokens.
|
12
13
|
def from_token(token)
|
13
14
|
Server.new_instance self, collection.find_one({ :_id=>token, :revoked=>nil })
|
@@ -16,9 +17,11 @@ module Rack
|
|
16
17
|
# Get an access token (create new one if necessary).
|
17
18
|
def get_token_for(identity, scope, client_id)
|
18
19
|
scope = scope.split.sort.join(" ") # Make sure always in same order.
|
19
|
-
|
20
|
-
|
21
|
-
|
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 })
|
22
|
+
token = { :_id=>Server.secure_random, :identity=>identity.to_s, :scope=>scope,
|
23
|
+
:client_id=>client_id, :created_at=>Time.now.utc.to_i,
|
24
|
+
:expires_at=>nil, :revoked=>nil }
|
22
25
|
collection.insert token
|
23
26
|
end
|
24
27
|
Server.new_instance self, token
|
@@ -29,6 +32,35 @@ module Rack
|
|
29
32
|
collection.find({ :identity=>identity }).map { |fields| Server.new_instance self, fields }
|
30
33
|
end
|
31
34
|
|
35
|
+
# Returns all access tokens for a given client, Use limit and offset
|
36
|
+
# to return a subset of tokens, sorted by creation date.
|
37
|
+
def for_client(client_id, offset = 0, limit = 100)
|
38
|
+
client_id = BSON::ObjectId(client_id.to_s)
|
39
|
+
collection.find({ :client_id=>client_id }, { :sort=>[[:created_at, Mongo::ASCENDING]], :skip=>offset, :limit=>limit }).
|
40
|
+
map { |token| Server.new_instance self, token }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns count of access tokens.
|
44
|
+
#
|
45
|
+
# @param [Hash] filter Count only a subset of access tokens
|
46
|
+
# @option filter [Integer] days Only count that many days (since now)
|
47
|
+
# @option filter [Boolean] revoked Only count revoked (true) or non-revoked (false) tokens; count all tokens if nil
|
48
|
+
# @option filter [String, ObjectId] client_id Only tokens grant to this client
|
49
|
+
def count(filter = {})
|
50
|
+
select = {}
|
51
|
+
if filter[:days]
|
52
|
+
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)
|
56
|
+
select[:revoked] = filter[:revoked] ? { :$ne=>nil } : { :$eq=>nil }
|
57
|
+
end
|
58
|
+
if filter[:client_id]
|
59
|
+
select[:client_id] = BSON::ObjectId(filter[:client_id].to_s)
|
60
|
+
end
|
61
|
+
collection.find(select).count
|
62
|
+
end
|
63
|
+
|
32
64
|
def collection
|
33
65
|
Server.database["oauth2.access_tokens"]
|
34
66
|
end
|
@@ -52,7 +84,7 @@ module Rack
|
|
52
84
|
|
53
85
|
# Revokes this access token.
|
54
86
|
def revoke!
|
55
|
-
self.revoked = Time.now.utc
|
87
|
+
self.revoked = Time.now.utc.to_i
|
56
88
|
AccessToken.collection.update({ :_id=>token }, { :$set=>{ :revoked=>revoked } })
|
57
89
|
end
|
58
90
|
|
@@ -18,7 +18,8 @@ module Rack
|
|
18
18
|
# and any state value to pass back in that redirect.
|
19
19
|
def create(client_id, scope, redirect_uri, response_type, state)
|
20
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, :grant_code=>nil,
|
21
|
+
:response_type=>response_type, :created_at=>Time.now.utc.to_i, :grant_code=>nil,
|
22
|
+
:authorized_at=>nil, :revoked=>nil }
|
22
23
|
fields[:_id] = collection.insert(fields)
|
23
24
|
Server.new_instance self, fields
|
24
25
|
end
|
@@ -56,7 +57,7 @@ module Rack
|
|
56
57
|
def grant!(identity)
|
57
58
|
raise ArgumentError, "Must supply a identity" unless identity
|
58
59
|
return if revoked
|
59
|
-
self.authorized_at = Time.now.utc
|
60
|
+
self.authorized_at = Time.now.utc.to_i
|
60
61
|
if response_type == "code" # Requested authorization code
|
61
62
|
access_grant = AccessGrant.create(identity, scope, client_id, redirect_uri)
|
62
63
|
self.grant_code = access_grant.code
|
@@ -71,7 +72,7 @@ module Rack
|
|
71
72
|
|
72
73
|
# Deny access.
|
73
74
|
def deny!
|
74
|
-
self.authorized_at = Time.now.utc
|
75
|
+
self.authorized_at = Time.now.utc.to_i
|
75
76
|
self.class.collection.update({ :_id=>id }, { :$set=>{ :authorized_at=>authorized_at } })
|
76
77
|
end
|
77
78
|
|
@@ -25,7 +25,8 @@ module Rack
|
|
25
25
|
def create(args)
|
26
26
|
redirect_uri = Server::Utils.parse_redirect_uri(args[:redirect_uri]).to_s if args[:redirect_uri]
|
27
27
|
fields = { :secret=>Server.secure_random, :display_name=>args[:display_name], :link=>args[:link],
|
28
|
-
:image_url=>args[:image_url], :redirect_uri=>redirect_uri, :created_at=>Time.now.utc,
|
28
|
+
:image_url=>args[:image_url], :redirect_uri=>redirect_uri, :created_at=>Time.now.utc.to_i,
|
29
|
+
:revoked=>nil }
|
29
30
|
fields[:_id] = collection.insert(fields)
|
30
31
|
Server.new_instance self, fields
|
31
32
|
end
|
@@ -38,6 +39,12 @@ module Rack
|
|
38
39
|
Server.new_instance self, collection.find_one({ :display_name=>field }) || collection.find_one({ :link=>field })
|
39
40
|
end
|
40
41
|
|
42
|
+
# Returns all the clients in the database, sorted alphabetically.
|
43
|
+
def all
|
44
|
+
collection.find({}, { :sort=>[[:display_name, Mongo::ASCENDING]] }).
|
45
|
+
map { |fields| Server.new_instance self, fields }
|
46
|
+
end
|
47
|
+
|
41
48
|
def collection
|
42
49
|
Server.database["oauth2.clients"]
|
43
50
|
end
|
@@ -65,13 +72,19 @@ module Rack
|
|
65
72
|
# Revoke all authorization requests, access grants and access tokens for
|
66
73
|
# this client. Ward off the evil.
|
67
74
|
def revoke!
|
68
|
-
self.revoked = Time.now.utc
|
75
|
+
self.revoked = Time.now.utc.to_i
|
69
76
|
Client.collection.update({ :_id=>id }, { :$set=>{ :revoked=>revoked } })
|
70
77
|
AuthRequest.collection.update({ :client_id=>id }, { :$set=>{ :revoked=>revoked } })
|
71
78
|
AccessGrant.collection.update({ :client_id=>id }, { :$set=>{ :revoked=>revoked } })
|
72
79
|
AccessToken.collection.update({ :client_id=>id }, { :$set=>{ :revoked=>revoked } })
|
73
80
|
end
|
74
81
|
|
82
|
+
def update(args)
|
83
|
+
fields = [:display_name, :link, :image_url].inject({}) { |h,k| v = args[k]; h[k] = v if v; h }
|
84
|
+
fields[:redirect_uri] = Server::Utils.parse_redirect_uri(args[:redirect_uri]).to_s if args[:redirect_uri]
|
85
|
+
self.class.collection.update({ :_id=>id }, { :$set=>fields })
|
86
|
+
end
|
87
|
+
|
75
88
|
Server.create_indexes do
|
76
89
|
# For quickly returning clients sorted by display name, or finding
|
77
90
|
# client from a URL.
|
data/lib/rack/oauth2/server.rb
CHANGED
@@ -2,7 +2,6 @@ require "rack/oauth2/models"
|
|
2
2
|
require "rack/oauth2/server/errors"
|
3
3
|
require "rack/oauth2/server/utils"
|
4
4
|
require "rack/oauth2/server/helper"
|
5
|
-
require "rack/oauth2/server/version"
|
6
5
|
|
7
6
|
|
8
7
|
module Rack
|
@@ -11,6 +10,9 @@ module Rack
|
|
11
10
|
# Implements an OAuth 2 Authorization Server, based on http://tools.ietf.org/html/draft-ietf-oauth-v2-10
|
12
11
|
class Server
|
13
12
|
|
13
|
+
# Same as gem version number.
|
14
|
+
VERSION = IO.read(::File.expand_path("../../../VERSION", ::File.dirname(__FILE__)))
|
15
|
+
|
14
16
|
class << self
|
15
17
|
# Return AuthRequest from authorization request handle.
|
16
18
|
def get_auth_request(authorization)
|
@@ -46,13 +48,17 @@ module Rack
|
|
46
48
|
# - :authorize_path -- Path for requesting end-user authorization. By
|
47
49
|
# convention defaults to /oauth/authorize.
|
48
50
|
# - :database -- Mongo::DB instance.
|
51
|
+
# - :host -- Only check requests sent to this host.
|
52
|
+
# - :path -- Only check requests for resources under this path.
|
53
|
+
# - :param_authentication -- If true, supports authentication using
|
54
|
+
# query/form parameters.
|
49
55
|
# - :realm -- Authorization realm that will show up in 401 responses.
|
50
56
|
# Defaults to use the request host name.
|
51
57
|
# - :scopes -- Array listing all supported scopes, e.g. %w{read write}.
|
52
58
|
# - :logger -- The logger to use. Under Rails, defaults to use the Rails
|
53
59
|
# logger. Will use Rack::Logger if available.
|
54
60
|
Options = Struct.new(:access_token_path, :authenticator, :authorization_types,
|
55
|
-
:authorize_path, :database, :realm, :scopes, :logger)
|
61
|
+
:authorize_path, :database, :host, :param_authentication, :path, :realm, :scopes, :logger)
|
56
62
|
|
57
63
|
def initialize(app, options = Options.new, &authenticator)
|
58
64
|
@app = app
|
@@ -61,76 +67,83 @@ module Rack
|
|
61
67
|
@options.access_token_path ||= "/oauth/access_token"
|
62
68
|
@options.authorize_path ||= "/oauth/authorize"
|
63
69
|
@options.authorization_types ||= %w{code token}
|
70
|
+
@options.param_authentication = false
|
64
71
|
end
|
65
72
|
|
66
73
|
# @see Options
|
67
74
|
attr_reader :options
|
68
75
|
|
69
76
|
def call(env)
|
70
|
-
# Use options.database if specified.
|
71
|
-
org_database, Server.database = Server.database, options.database || Server.database
|
72
|
-
logger = options.logger || env["rack.logger"]
|
73
77
|
request = OAuthRequest.new(env)
|
78
|
+
return @app.call(env) if options.host && options.host != request.host
|
79
|
+
return @app.call(env) if options.path && request.path.index(options.path) != 0
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
return respond_with_access_token(request, logger) if request.path == options.access_token_path
|
80
|
-
|
81
|
-
# 5. Accessing a Protected Resource
|
82
|
-
if request.authorization
|
83
|
-
# 5.1.1. The Authorization Request Header Field
|
84
|
-
token = request.credentials if request.oauth?
|
85
|
-
else
|
86
|
-
# 5.1.2. URI Query Parameter
|
87
|
-
# 5.1.3. Form-Encoded Body Parameter
|
88
|
-
token = request.GET["oauth_token"] || request.POST["oauth_token"]
|
89
|
-
end
|
81
|
+
begin
|
82
|
+
# Use options.database if specified.
|
83
|
+
org_database, Server.database = Server.database, options.database || Server.database
|
84
|
+
logger = options.logger || env["rack.logger"]
|
90
85
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
return unauthorized(request)
|
86
|
+
# 3. Obtaining End-User Authorization
|
87
|
+
# Flow starts here.
|
88
|
+
return request_authorization(request, logger) if request.path == options.authorize_path
|
89
|
+
# 4. Obtaining an Access Token
|
90
|
+
return respond_with_access_token(request, logger) if request.path == options.access_token_path
|
91
|
+
|
92
|
+
# 5. Accessing a Protected Resource
|
93
|
+
if request.authorization
|
94
|
+
# 5.1.1. The Authorization Request Header Field
|
95
|
+
token = request.credentials if request.oauth?
|
96
|
+
elsif options.param_authentication && !request.GET["oauth_verifier"] # Ignore OAuth 1.0 callbacks
|
97
|
+
# 5.1.2. URI Query Parameter
|
98
|
+
# 5.1.3. Form-Encoded Body Parameter
|
99
|
+
token = request.GET["oauth_token"] || request.POST["oauth_token"]
|
106
100
|
end
|
107
101
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
#
|
126
|
-
#
|
127
|
-
|
102
|
+
if token
|
103
|
+
begin
|
104
|
+
access_token = AccessToken.from_token(token)
|
105
|
+
raise InvalidTokenError if access_token.nil? || access_token.revoked
|
106
|
+
raise ExpiredTokenError if access_token.expires_at && access_token.expires_at <= Time.now.utc
|
107
|
+
request.env["oauth.access_token"] = token
|
108
|
+
request.env["oauth.identity"] = access_token.identity
|
109
|
+
logger.info "Authorized #{access_token.identity}" if logger
|
110
|
+
rescue Error=>error
|
111
|
+
# 5.2. The WWW-Authenticate Response Header Field
|
112
|
+
logger.info "HTTP authorization failed #{error.code}" if logger
|
113
|
+
return unauthorized(request, error)
|
114
|
+
rescue =>ex
|
115
|
+
logger.info "HTTP authorization failed #{ex.message}" if logger
|
116
|
+
return unauthorized(request)
|
117
|
+
end
|
118
|
+
|
119
|
+
# We expect application to use 403 if request has insufficient scope,
|
120
|
+
# and return appropriate WWW-Authenticate header.
|
121
|
+
response = @app.call(env)
|
122
|
+
if response[0] == 403
|
123
|
+
scope = response[1]["oauth.no_scope"] || ""
|
124
|
+
scope = scope.join(" ") if scope.respond_to?(:join)
|
125
|
+
challenge = 'OAuth realm="%s", error="insufficient_scope", scope="%s"' % [(options.realm || request.host), scope]
|
126
|
+
response[1]["WWW-Authenticate"] = challenge
|
127
|
+
return response
|
128
|
+
else
|
129
|
+
return response
|
130
|
+
end
|
128
131
|
else
|
129
|
-
|
132
|
+
response = @app.call(env)
|
133
|
+
if response[1] && response[1].delete("oauth.no_access")
|
134
|
+
# OAuth access required.
|
135
|
+
return unauthorized(request)
|
136
|
+
elsif response[1] && response[1]["oauth.authorization"]
|
137
|
+
# 3. Obtaining End-User Authorization
|
138
|
+
# Flow ends here.
|
139
|
+
return authorization_response(response, logger)
|
140
|
+
else
|
141
|
+
return response
|
142
|
+
end
|
130
143
|
end
|
144
|
+
ensure
|
145
|
+
Server.database = org_database
|
131
146
|
end
|
132
|
-
ensure
|
133
|
-
Server.database = org_database
|
134
147
|
end
|
135
148
|
|
136
149
|
protected
|