oauth2-provider-jonrowe 0.1.0

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