rack-oauth2-server 1.2.2 → 1.3.0
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 +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
|