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.
Files changed (45) hide show
  1. data/README.rdoc +314 -0
  2. data/example/README.rdoc +11 -0
  3. data/example/application.rb +151 -0
  4. data/example/config.ru +3 -0
  5. data/example/environment.rb +11 -0
  6. data/example/models/connection.rb +9 -0
  7. data/example/models/note.rb +4 -0
  8. data/example/models/user.rb +6 -0
  9. data/example/public/style.css +78 -0
  10. data/example/schema.rb +27 -0
  11. data/example/views/authorize.erb +28 -0
  12. data/example/views/create_user.erb +3 -0
  13. data/example/views/home.erb +25 -0
  14. data/example/views/layout.erb +25 -0
  15. data/example/views/login.erb +20 -0
  16. data/example/views/new_client.erb +25 -0
  17. data/example/views/new_user.erb +22 -0
  18. data/example/views/show_client.erb +15 -0
  19. data/lib/oauth2/model.rb +17 -0
  20. data/lib/oauth2/model/authorization.rb +113 -0
  21. data/lib/oauth2/model/client.rb +55 -0
  22. data/lib/oauth2/model/client_owner.rb +13 -0
  23. data/lib/oauth2/model/hashing.rb +27 -0
  24. data/lib/oauth2/model/resource_owner.rb +26 -0
  25. data/lib/oauth2/model/schema.rb +42 -0
  26. data/lib/oauth2/provider.rb +117 -0
  27. data/lib/oauth2/provider/access_token.rb +66 -0
  28. data/lib/oauth2/provider/authorization.rb +168 -0
  29. data/lib/oauth2/provider/error.rb +29 -0
  30. data/lib/oauth2/provider/exchange.rb +212 -0
  31. data/lib/oauth2/router.rb +60 -0
  32. data/spec/factories.rb +27 -0
  33. data/spec/oauth2/model/authorization_spec.rb +216 -0
  34. data/spec/oauth2/model/client_spec.rb +55 -0
  35. data/spec/oauth2/model/resource_owner_spec.rb +55 -0
  36. data/spec/oauth2/provider/access_token_spec.rb +125 -0
  37. data/spec/oauth2/provider/authorization_spec.rb +323 -0
  38. data/spec/oauth2/provider/exchange_spec.rb +330 -0
  39. data/spec/oauth2/provider_spec.rb +531 -0
  40. data/spec/request_helpers.rb +46 -0
  41. data/spec/spec_helper.rb +44 -0
  42. data/spec/test_app/helper.rb +33 -0
  43. data/spec/test_app/provider/application.rb +61 -0
  44. data/spec/test_app/provider/views/authorize.erb +19 -0
  45. metadata +220 -0
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/application'
2
+ run Sinatra::Application
3
+
@@ -0,0 +1,11 @@
1
+ dir = File.expand_path(File.dirname(__FILE__))
2
+ $:.unshift(dir + '/../lib')
3
+ $:.unshift(dir)
4
+
5
+ require 'oauth2/provider'
6
+ OAuth2::Provider.realm = 'Notes App'
7
+
8
+ require 'models/connection'
9
+ require 'models/user'
10
+ require 'models/note'
11
+
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'active_record'
3
+
4
+ dir = File.expand_path(File.dirname(__FILE__))
5
+
6
+ ActiveRecord::Base.establish_connection(
7
+ :adapter => 'sqlite3',
8
+ :database => dir + '/../db/notes.sqlite3')
9
+
@@ -0,0 +1,4 @@
1
+ class Note < ActiveRecord::Base
2
+ belongs_to :user
3
+ end
4
+
@@ -0,0 +1,6 @@
1
+ class User < ActiveRecord::Base
2
+ include OAuth2::Model::ResourceOwner
3
+ include OAuth2::Model::ClientOwner
4
+ has_many :notes
5
+ end
6
+
@@ -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
+
@@ -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,3 @@
1
+ <h3>New User Created</h3>
2
+
3
+ <p>Your username is: <%= @user.username %></p>
@@ -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&rsquo;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&rsquo;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&rsquo;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> &mdash; returns the current user&rsquo;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&rsquo;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 &copy; 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&rsquo;d ask for a password usually, but seeing as
4
+ it&rsquo;s <em>you</em>&hellip;</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
+
@@ -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
+