oauth2-provider-jonrowe 0.1.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/README.rdoc +314 -0
- data/example/README.rdoc +11 -0
- data/example/application.rb +151 -0
- data/example/config.ru +3 -0
- data/example/environment.rb +11 -0
- data/example/models/connection.rb +9 -0
- data/example/models/note.rb +4 -0
- data/example/models/user.rb +6 -0
- data/example/public/style.css +78 -0
- data/example/schema.rb +27 -0
- data/example/views/authorize.erb +28 -0
- data/example/views/create_user.erb +3 -0
- data/example/views/home.erb +25 -0
- data/example/views/layout.erb +25 -0
- data/example/views/login.erb +20 -0
- data/example/views/new_client.erb +25 -0
- data/example/views/new_user.erb +22 -0
- data/example/views/show_client.erb +15 -0
- data/lib/oauth2/model.rb +17 -0
- data/lib/oauth2/model/authorization.rb +113 -0
- data/lib/oauth2/model/client.rb +55 -0
- data/lib/oauth2/model/client_owner.rb +13 -0
- data/lib/oauth2/model/hashing.rb +27 -0
- data/lib/oauth2/model/resource_owner.rb +26 -0
- data/lib/oauth2/model/schema.rb +42 -0
- data/lib/oauth2/provider.rb +117 -0
- data/lib/oauth2/provider/access_token.rb +66 -0
- data/lib/oauth2/provider/authorization.rb +168 -0
- data/lib/oauth2/provider/error.rb +29 -0
- data/lib/oauth2/provider/exchange.rb +212 -0
- data/lib/oauth2/router.rb +60 -0
- data/spec/factories.rb +27 -0
- data/spec/oauth2/model/authorization_spec.rb +216 -0
- data/spec/oauth2/model/client_spec.rb +55 -0
- data/spec/oauth2/model/resource_owner_spec.rb +55 -0
- data/spec/oauth2/provider/access_token_spec.rb +125 -0
- data/spec/oauth2/provider/authorization_spec.rb +323 -0
- data/spec/oauth2/provider/exchange_spec.rb +330 -0
- data/spec/oauth2/provider_spec.rb +531 -0
- data/spec/request_helpers.rb +46 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/test_app/helper.rb +33 -0
- data/spec/test_app/provider/application.rb +61 -0
- data/spec/test_app/provider/views/authorize.erb +19 -0
- metadata +220 -0
data/example/config.ru
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
body {
|
2
|
+
font: 16px/1.4 FreeSans, Helvetica, Arial, sans-serif;
|
3
|
+
background: #353e4b;
|
4
|
+
}
|
5
|
+
|
6
|
+
.sub {
|
7
|
+
width: 640px;
|
8
|
+
margin: 0 auto;
|
9
|
+
padding: 1em 2em;
|
10
|
+
}
|
11
|
+
|
12
|
+
.header {
|
13
|
+
text-shadow: #23282e 0px -2px 0px;
|
14
|
+
}
|
15
|
+
|
16
|
+
.header h1 {
|
17
|
+
color: #e5dec7;
|
18
|
+
font-size: 4em;
|
19
|
+
letter-spacing: -0.06em;
|
20
|
+
margin: 0;
|
21
|
+
}
|
22
|
+
|
23
|
+
.header h2 {
|
24
|
+
color: #b3a784;
|
25
|
+
font-size: 1.5em;
|
26
|
+
font-weight: normal;
|
27
|
+
letter-spacing: -0.06em;
|
28
|
+
margin: 0 0 0.5em;
|
29
|
+
}
|
30
|
+
|
31
|
+
.content .sub {
|
32
|
+
background: #fff;
|
33
|
+
color: #333;
|
34
|
+
-webkit-border-radius: 16px;
|
35
|
+
-moz-border-radius: 16px;
|
36
|
+
border-radius: 16px;
|
37
|
+
}
|
38
|
+
|
39
|
+
h3 {
|
40
|
+
color: #888;
|
41
|
+
font-size: 1.5em;
|
42
|
+
font-weight: normal;
|
43
|
+
margin: 0 0 1em;
|
44
|
+
}
|
45
|
+
|
46
|
+
fieldset {
|
47
|
+
border: none;
|
48
|
+
border-top: 1px solid #ccc;
|
49
|
+
padding: 12px 0 0 0;
|
50
|
+
margin: 12px 0 0 0;
|
51
|
+
}
|
52
|
+
|
53
|
+
table {
|
54
|
+
border-collapse: collapse;
|
55
|
+
}
|
56
|
+
|
57
|
+
table th, table td {
|
58
|
+
border-top: 1px solid #eee;
|
59
|
+
padding: 8px 16px;
|
60
|
+
}
|
61
|
+
|
62
|
+
table th {
|
63
|
+
border-right: 2px solid #ccc;
|
64
|
+
text-align: left;
|
65
|
+
}
|
66
|
+
|
67
|
+
a {
|
68
|
+
color: #9ba749;
|
69
|
+
font-weight: bold;
|
70
|
+
text-decoration: none;
|
71
|
+
}
|
72
|
+
|
73
|
+
.footer {
|
74
|
+
font-size: 0.8em;
|
75
|
+
color: #999;
|
76
|
+
text-shadow: #23282e 0px -1px 0px;
|
77
|
+
}
|
78
|
+
|
data/example/schema.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
dir = File.expand_path(File.dirname(__FILE__))
|
2
|
+
$:.unshift(dir + '/../lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'oauth2/provider'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
require dir + '/models/connection'
|
9
|
+
|
10
|
+
FileUtils.mkdir_p(dir + '/db')
|
11
|
+
|
12
|
+
ActiveRecord::Schema.define do |version|
|
13
|
+
create_table :users, :force => true do |t|
|
14
|
+
t.timestamps
|
15
|
+
t.string :username
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :notes, :force => true do |t|
|
19
|
+
t.timestamps
|
20
|
+
t.belongs_to :user
|
21
|
+
t.string :title
|
22
|
+
t.text :body
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
OAuth2::Model::Schema.up
|
27
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<h3>Authorize OAuth client</h3>
|
2
|
+
|
3
|
+
<p>This application <b><%= @oauth2.client.name %></b> wants
|
4
|
+
the following permissions:</p>
|
5
|
+
|
6
|
+
<ul>
|
7
|
+
<% @oauth2.scopes.each do |scope| %>
|
8
|
+
<% next unless PERMISSIONS[scope] %>
|
9
|
+
<li><%= PERMISSIONS[scope] %></li>
|
10
|
+
<% end %>
|
11
|
+
</ul>
|
12
|
+
|
13
|
+
<form method="post" action="/oauth/allow">
|
14
|
+
<% @oauth2.params.each do |key, value| %>
|
15
|
+
<input type="hidden" name="<%= key %>" value="<%= value %>">
|
16
|
+
<% end %>
|
17
|
+
<input type="hidden" name="user_id" value="<%= @user.id %>">
|
18
|
+
|
19
|
+
<fieldset>
|
20
|
+
<input type="checkbox" name="allow" id="allow" value="1">
|
21
|
+
<label for="allow">Allow this application</label>
|
22
|
+
</fieldset>
|
23
|
+
|
24
|
+
<fieldset>
|
25
|
+
<input type="submit" value="Go!">
|
26
|
+
</fieldset>
|
27
|
+
</form>
|
28
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<p>Welcome to the <b>Songkick OAuth 2.0 demo</b>. The endpoint you should direct
|
2
|
+
users to is:</p>
|
3
|
+
|
4
|
+
<pre> <%= host %>/oauth/authorize</pre>
|
5
|
+
|
6
|
+
<p>This handles both user authorization and token exchange requests. Before you
|
7
|
+
can use this though, you’ll need to register your application.</p>
|
8
|
+
|
9
|
+
<ul>
|
10
|
+
<li><a href="/oauth/apps/new">Register your application</a></li>
|
11
|
+
</ul>
|
12
|
+
|
13
|
+
<p>This application is a note-taking app. It exposes a JSON API for reading a
|
14
|
+
user’s notes, but you need an access token for this. Use the OAuth
|
15
|
+
protocol to get one, or see if you can hack in without permission!</p>
|
16
|
+
|
17
|
+
<p>The following resources are available. You’ll need to ask the user for
|
18
|
+
the <b><code>read_notes</code></b> permission scope to get at them.</p>
|
19
|
+
|
20
|
+
<ul>
|
21
|
+
<li><code>/me</code> — returns the current user’s data</li>
|
22
|
+
<li><code>/users/:username/notes</code></li>
|
23
|
+
<li><code>/users/:username/notes/:note_id</code></li>
|
24
|
+
</ul>
|
25
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
5
|
+
<title>OAuth 2.0 demo</title>
|
6
|
+
<link rel="stylesheet" href="/style.css">
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
|
10
|
+
<div class="header"><div class="sub">
|
11
|
+
<h1>OAuth 2.0 demo</h1>
|
12
|
+
<h2>Steal my notes, why don’t you</h2>
|
13
|
+
</div></div>
|
14
|
+
|
15
|
+
<div class="content"><div class="sub">
|
16
|
+
<%= yield %>
|
17
|
+
</div></div>
|
18
|
+
|
19
|
+
<div class="footer"><div class="sub">
|
20
|
+
<p>Copyright © 2010 Songkick.com</p>
|
21
|
+
</div></div>
|
22
|
+
|
23
|
+
</body>
|
24
|
+
</html>
|
25
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<h3>Sign in</h3>
|
2
|
+
|
3
|
+
<p>Who are you? We’d ask for a password usually, but seeing as
|
4
|
+
it’s <em>you</em>…</p>
|
5
|
+
|
6
|
+
<form method="post" action="/login">
|
7
|
+
<% @oauth2.params.each do |key, value| %>
|
8
|
+
<input type="hidden" name="<%= key %>" value="<%= value %>">
|
9
|
+
<% end %>
|
10
|
+
|
11
|
+
<fieldset>
|
12
|
+
<label for="username">Username</label>
|
13
|
+
<input type="text" name="username" id="username">
|
14
|
+
</fieldset>
|
15
|
+
|
16
|
+
<fieldset>
|
17
|
+
<input type="submit" value="Sign in">
|
18
|
+
</fieldset>
|
19
|
+
</form>
|
20
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<h3>Register you application</h3>
|
2
|
+
|
3
|
+
<% if @client.errors.any? %>
|
4
|
+
<ul class="errors">
|
5
|
+
<% @client.errors.full_messages.each do |message| %>
|
6
|
+
<li><%= message %></li>
|
7
|
+
<% end %>
|
8
|
+
</ul>
|
9
|
+
<% end %>
|
10
|
+
|
11
|
+
<form method="post" action="/oauth/apps">
|
12
|
+
<fieldset>
|
13
|
+
<label for="name">Application name</label>
|
14
|
+
<input type="text" name="name" id="name">
|
15
|
+
</fieldset>
|
16
|
+
<fieldset>
|
17
|
+
<label for="redirect_uri">Callback URI</label>
|
18
|
+
<input type="text" name="redirect_uri" id="redirect_uri">
|
19
|
+
</fieldset>
|
20
|
+
|
21
|
+
<fieldset>
|
22
|
+
<input type="submit" value="Register">
|
23
|
+
</fieldset>
|
24
|
+
</form>
|
25
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<h3>Register a User</h3>
|
2
|
+
|
3
|
+
<% if @user.errors.any? %>
|
4
|
+
<ul class="errors">
|
5
|
+
<% @user.errors.full_messages.each do |message| %>
|
6
|
+
<li><%= message %></li>
|
7
|
+
<% end %>
|
8
|
+
</ul>
|
9
|
+
<% end %>
|
10
|
+
|
11
|
+
<form method="post" action="/users/create">
|
12
|
+
<fieldset>
|
13
|
+
<label for="name">Username</label>
|
14
|
+
<input type="text" name="username" id="username">
|
15
|
+
</fieldset>
|
16
|
+
|
17
|
+
<fieldset>
|
18
|
+
<input type="submit" value="Register">
|
19
|
+
</fieldset>
|
20
|
+
</form>
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<h3>Client app: <%= @client.name %></h3>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tbody>
|
5
|
+
<tr>
|
6
|
+
<th scope="row">client_id</th>
|
7
|
+
<td><%= @client.client_id %></td>
|
8
|
+
</tr>
|
9
|
+
<tr>
|
10
|
+
<th scope="row">client_secret</th>
|
11
|
+
<td><%= @client_secret %></td>
|
12
|
+
</tr>
|
13
|
+
</tbody>
|
14
|
+
</table>
|
15
|
+
|
data/lib/oauth2/model.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module OAuth2
|
4
|
+
module Model
|
5
|
+
autoload :ClientOwner, ROOT + '/oauth2/model/client_owner'
|
6
|
+
autoload :ResourceOwner, ROOT + '/oauth2/model/resource_owner'
|
7
|
+
autoload :Hashing, ROOT + '/oauth2/model/hashing'
|
8
|
+
autoload :Authorization, ROOT + '/oauth2/model/authorization'
|
9
|
+
autoload :Client, ROOT + '/oauth2/model/client'
|
10
|
+
autoload :Schema, ROOT + '/oauth2/model/schema'
|
11
|
+
|
12
|
+
def self.find_access_token(access_token)
|
13
|
+
Authorization.find_by_access_token_hash(OAuth2.hashify(access_token))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module OAuth2
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class Authorization < ActiveRecord::Base
|
5
|
+
set_table_name :oauth2_authorizations
|
6
|
+
|
7
|
+
belongs_to :oauth2_resource_owner, :polymorphic => true
|
8
|
+
alias :owner :oauth2_resource_owner
|
9
|
+
alias :owner= :oauth2_resource_owner=
|
10
|
+
|
11
|
+
belongs_to :client, :class_name => 'OAuth2::Model::Client'
|
12
|
+
|
13
|
+
validates_presence_of :client, :owner
|
14
|
+
|
15
|
+
validates_uniqueness_of :code, :scope => :client_id, :allow_nil => true
|
16
|
+
validates_uniqueness_of :refresh_token_hash, :scope => :client_id, :allow_nil => true
|
17
|
+
validates_uniqueness_of :access_token_hash, :allow_nil => true
|
18
|
+
|
19
|
+
extend Hashing
|
20
|
+
hashes_attributes :access_token, :refresh_token
|
21
|
+
|
22
|
+
def self.for(resource_owner, client)
|
23
|
+
return nil unless resource_owner and client
|
24
|
+
resource_owner.oauth2_authorizations.find_by_client_id(client.id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create_code(client)
|
28
|
+
OAuth2.generate_id do |code|
|
29
|
+
client.authorizations.count(:conditions => {:code => code}).zero?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.create_access_token
|
34
|
+
OAuth2.generate_id do |token|
|
35
|
+
hash = OAuth2.hashify(token)
|
36
|
+
count(:conditions => {:access_token_hash => hash}).zero?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.create_refresh_token(client)
|
41
|
+
OAuth2.generate_id do |refresh_token|
|
42
|
+
hash = OAuth2.hashify(refresh_token)
|
43
|
+
client.authorizations.count(:conditions => {:refresh_token_hash => hash}).zero?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.for_response_type(response_type, attributes = {})
|
48
|
+
instance = self.for(attributes[:owner], attributes[:client]) ||
|
49
|
+
new(:owner => attributes[:owner], :client => attributes[:client])
|
50
|
+
|
51
|
+
case response_type
|
52
|
+
when CODE
|
53
|
+
instance.code ||= create_code(attributes[:client])
|
54
|
+
when TOKEN
|
55
|
+
instance.access_token ||= create_access_token
|
56
|
+
instance.refresh_token ||= create_refresh_token(attributes[:client])
|
57
|
+
when CODE_AND_TOKEN
|
58
|
+
instance.code = create_code(attributes[:client])
|
59
|
+
instance.access_token ||= create_access_token
|
60
|
+
instance.refresh_token ||= create_refresh_token(attributes[:client])
|
61
|
+
end
|
62
|
+
|
63
|
+
if attributes[:duration]
|
64
|
+
instance.expires_at = Time.now + attributes[:duration].to_i
|
65
|
+
else
|
66
|
+
instance.expires_at = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
if attributes[:scope]
|
70
|
+
scopes = instance.scopes + attributes[:scope].split(/\s+/)
|
71
|
+
instance.scope = scopes.join(' ')
|
72
|
+
end
|
73
|
+
|
74
|
+
instance.save && instance
|
75
|
+
end
|
76
|
+
|
77
|
+
def exchange!
|
78
|
+
self.code = nil
|
79
|
+
self.access_token = self.class.create_access_token
|
80
|
+
self.refresh_token = nil
|
81
|
+
save!
|
82
|
+
end
|
83
|
+
|
84
|
+
def expired?
|
85
|
+
return false unless expires_at
|
86
|
+
expires_at < Time.now
|
87
|
+
end
|
88
|
+
|
89
|
+
def expires_in
|
90
|
+
expires_at && (expires_at - Time.now).ceil
|
91
|
+
end
|
92
|
+
|
93
|
+
def generate_code
|
94
|
+
self.code ||= self.class.create_code(client)
|
95
|
+
save && code
|
96
|
+
end
|
97
|
+
|
98
|
+
def grants_access?(user, *scopes)
|
99
|
+
not expired? and user == owner and in_scope?(scopes)
|
100
|
+
end
|
101
|
+
|
102
|
+
def in_scope?(request_scope)
|
103
|
+
[*request_scope].all?(&scopes.method(:include?))
|
104
|
+
end
|
105
|
+
|
106
|
+
def scopes
|
107
|
+
scope ? scope.split(/\s+/) : []
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|