oauth2-provider-jonrowe 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|