rockoauth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/History.txt +5 -0
  3. data/README.rdoc +422 -0
  4. data/example/README.rdoc +11 -0
  5. data/example/application.rb +158 -0
  6. data/example/config.ru +3 -0
  7. data/example/environment.rb +11 -0
  8. data/example/models/connection.rb +9 -0
  9. data/example/models/note.rb +4 -0
  10. data/example/models/user.rb +5 -0
  11. data/example/public/style.css +78 -0
  12. data/example/schema.rb +22 -0
  13. data/example/views/authorize.erb +28 -0
  14. data/example/views/create_user.erb +3 -0
  15. data/example/views/error.erb +6 -0
  16. data/example/views/home.erb +24 -0
  17. data/example/views/layout.erb +24 -0
  18. data/example/views/login.erb +20 -0
  19. data/example/views/new_client.erb +25 -0
  20. data/example/views/new_user.erb +22 -0
  21. data/example/views/show_client.erb +15 -0
  22. data/lib/rockoauth/model/authorization.rb +132 -0
  23. data/lib/rockoauth/model/client.rb +54 -0
  24. data/lib/rockoauth/model/client_owner.rb +13 -0
  25. data/lib/rockoauth/model/hashing.rb +26 -0
  26. data/lib/rockoauth/model/helpers.rb +14 -0
  27. data/lib/rockoauth/model/resource_owner.rb +22 -0
  28. data/lib/rockoauth/model.rb +38 -0
  29. data/lib/rockoauth/provider/access_token.rb +70 -0
  30. data/lib/rockoauth/provider/authorization.rb +185 -0
  31. data/lib/rockoauth/provider/error.rb +19 -0
  32. data/lib/rockoauth/provider/exchange.rb +225 -0
  33. data/lib/rockoauth/provider.rb +133 -0
  34. data/lib/rockoauth/router.rb +75 -0
  35. data/lib/rockoauth/schema/20120828112156_rockoauth_schema_original_schema.rb +35 -0
  36. data/lib/rockoauth/schema/20121024180930_rockoauth_schema_add_authorization_index.rb +13 -0
  37. data/lib/rockoauth/schema/20121025180447_rockoauth_schema_add_unique_indexes.rb +31 -0
  38. data/lib/rockoauth/schema.rb +25 -0
  39. data/lib/rockoauth.rb +1 -0
  40. data/spec/factories.rb +20 -0
  41. data/spec/request_helpers.rb +62 -0
  42. data/spec/rockoauth/model/authorization_spec.rb +237 -0
  43. data/spec/rockoauth/model/client_spec.rb +44 -0
  44. data/spec/rockoauth/model/helpers_spec.rb +25 -0
  45. data/spec/rockoauth/model/resource_owner_spec.rb +87 -0
  46. data/spec/rockoauth/provider/access_token_spec.rb +138 -0
  47. data/spec/rockoauth/provider/authorization_spec.rb +356 -0
  48. data/spec/rockoauth/provider/exchange_spec.rb +361 -0
  49. data/spec/rockoauth/provider_spec.rb +560 -0
  50. data/spec/spec_helper.rb +80 -0
  51. data/spec/test_app/helper.rb +36 -0
  52. data/spec/test_app/provider/application.rb +67 -0
  53. data/spec/test_app/provider/views/authorize.erb +19 -0
  54. metadata +238 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0081b3bf71486478b9f762a2f5979873c5127188
4
+ data.tar.gz: b37c6e24f0db4c94433434ebe00d1b85be3b7918
5
+ SHA512:
6
+ metadata.gz: 5c728f55b464f77e08da65dc544415715d949e3bea3f45f3a64e28efc8e01a4e87c6e481960f64e3d469f542b98d7f70a56072164fe963cd49c22ee8da71f0f5
7
+ data.tar.gz: 882574b6393c884f22f46c07c367676ffba3bf4334a0d7f5b7156334e963452a6211c6c6fd8baf8b29d7385ce9616a33b173feb0eedf7f8228a8f7159b90baeb
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ === 0.1.0 / 2014-07-08
2
+
3
+ * Forked from songkick-oauth2-provider
4
+ * Persist the current resource owner through the assertion handler
5
+ * Remove `attr_accessible` for Rails 4 compatibility.
data/README.rdoc ADDED
@@ -0,0 +1,422 @@
1
+ = RockOAuth Oauth 2 Provider
2
+
3
+ This gem provides a toolkit for adding OAuth2 provider capabilities to a Ruby
4
+ web app. It handles most of the protocol for you: it is designed to provide
5
+ a sufficient level of abstraction that it can implement updates to the protocol
6
+ without affecting your application code at all. All you have to deal with is
7
+ authenticating your users and letting them grant access to client apps.
8
+
9
+ It is also designed to be usable within any web frontend, at least those of
10
+ Rails and Sinatra. Its API uses Rack request-environment hashes rather than
11
+ framework-specific request objects, though you can pass those in and their
12
+ <tt>request.env</tt> property will be used internally.
13
+
14
+ It stores the clients and authorizations using ActiveRecord.
15
+
16
+ It is forked from songkick-oauth2-provider[https://github.com/songkick/oauth2-provider].
17
+ With much appreciation for their excellent work.
18
+
19
+ === Installation
20
+
21
+ gem install rockoauth
22
+
23
+ === A note on versioning
24
+
25
+ This library is based on draft-10[http://tools.ietf.org/html/draft-ietf-oauth-v2-10],
26
+ which was current when we began writing it. Having observed the development of
27
+ the OAuth 2.0 spec over time, we have decided not to upgrade to later drafts
28
+ until the spec is finalized. There is not enough meaningful change going on to
29
+ merit migrating to every draft version - it would simply create a lot of
30
+ turbulence and make it hard to reason about exactly what semantics the library
31
+ supports.
32
+
33
+ During draft state, the gem version will indicate which draft it implements
34
+ using the minor version, for example <tt>0.10.2</tt> means the second bug-fix
35
+ release for draft 10.
36
+
37
+
38
+ == Terminology
39
+
40
+ * <b>Client</b>: A third-party software system that integrates with the provider.
41
+ Twitter and Facebook call this an "app".
42
+ * <b>Client Owner</b>: The entity which owns a <b>client</b>, i.e. the
43
+ individual or company responsible for the client application.
44
+ * <b>Resource Owner</b>: This will almost certainly be a User. It's the entity
45
+ which has the data that the <b>client</b> is asking permission to see.
46
+ * <b>Authorization</b>: When a <b>resource owner</b> grants access to a
47
+ <b>client</b> (i.e., a user grants access to a company's app), an
48
+ authorization is created. This can typically be revoked by the user at any
49
+ time (which is the strength and flexibility of the OAuth architecture).
50
+ * <b>Access Token</b>: An opaque string representing an <b>authorization</b>.
51
+ A <b>client</b> is given an access token when a <b>resource owner</b> grants
52
+ it access to resources. The access token must be included in all requests for
53
+ protected resources.
54
+
55
+
56
+ == Usage
57
+
58
+ A basic example is in <tt>example/application.rb</tt>. To implement OAuth, you
59
+ need to provide four things:
60
+
61
+ * Some UI to register client applications
62
+ * The OAuth request endpoint
63
+ * A flow for logged-in users to grant access to clients
64
+ * Resources protected by access tokens
65
+
66
+
67
+ === Declare your app's name
68
+
69
+ Declare your app's name somewhere (for example in Rails, in <tt>application.rb</tt>
70
+ or an initializer):
71
+
72
+ require 'rockoauth/provider'
73
+ RockOAuth::Provider.realm = 'My OAuth app'
74
+
75
+
76
+ === HTTPS
77
+
78
+ Your application should ensure that any endpoint that receives or returns OAuth
79
+ data is only accessible over a secure transport such as the <tt>https:</tt>
80
+ protocol. <tt>RockOAuth::Provider</tt> can enforce this to make it easier
81
+ to keep your users' data secure.
82
+
83
+ You can set <tt>RockOAuth::Provider.enforce_ssl = true</tt> in the same
84
+ place that you declared your app name above. This will result in the following
85
+ behavior:
86
+
87
+ * The <tt>RockOAuth::Provider.parse</tt> method will produce error
88
+ responses and will not process the incoming request unless the request was
89
+ made using the <tt>https:</tt> protocol.
90
+ * An access token constructed using <tt>RockOAuth::Provider.access_token</tt>
91
+ will return <tt>false</tt> for <tt>#valid?</tt> unless the request was made
92
+ using the <tt>https:</tt> protocol.
93
+ * Any access token received over an insecure connection is immediately destroyed
94
+ to prevent eavesdroppers getting access to the user's resources. A client
95
+ making an insecure request will have to send the user through the
96
+ authorization process again to get a new token.
97
+
98
+
99
+ === Schema
100
+
101
+ Add the <tt>RockOAuth::Provider</tt> tables to your app's schema. This is
102
+ done using <tt>RockOAuth::Model::Schema.migrate</tt>, which will run all
103
+ the gem's migrations that have not yet been applied to your database.
104
+
105
+ RockOAuth::Model::Schema.migrate
106
+ I, [2012-10-31T14:52:33.801428 #7002] INFO -- : Migrating to RockoauthSchemaOriginalSchema (20120828112156)
107
+ == Rockoauth2SchemaOriginalSchema: migrating =============================
108
+ -- create_table(:oauth2_clients)
109
+ -> 0.0029s
110
+ -- add_index(:oauth2_clients, [:client_id])
111
+ -> 0.0009s
112
+ ...
113
+
114
+ To rollback migrations, use <tt>RockOAuth::Model::Schema.rollback</tt>.
115
+
116
+
117
+ === Model Mixins
118
+
119
+ There are two mixins you need to put in your code,
120
+ <tt>RockOAuth::Model::ClientOwner</tt> for whichever model will own the
121
+ "apps", and <tt>RockOAuth::Model::ResourceOwner</tt> for whichever model
122
+ is the innocent, unassuming entity who will selectively share their data. It's
123
+ possible that this is the same model, such as User:
124
+
125
+ class User < ActiveRecord::Base
126
+ include RockOAuth::Model::ResourceOwner
127
+ include RockOAuth::Model::ClientOwner
128
+ has_many :interesting_pieces_of_data
129
+ end
130
+
131
+ Or they might go into two different models:
132
+
133
+ class User < ActiveRecord::Base
134
+ include RockOAuth::Model::ResourceOwner
135
+ has_many :interesting_pieces_of_data
136
+ end
137
+
138
+ class Company < ActiveRecord::Base
139
+ include RockOAuth::Model::ClientOwner
140
+ belongs_to :user
141
+ end
142
+
143
+ To see the methods and associations that these two mixins add to your models,
144
+ take a look at <b>lib/oauth2/model/client_owner.rb</b> and
145
+ <b>lib/oauth2/model/resource_owner.rb</b>.
146
+
147
+
148
+ === Registering client applications
149
+
150
+ Clients are modeled by the <tt>RockOAuth::Model::Client</tt> class, which
151
+ is an ActiveRecord model. You just need to implement a UI for creating them, for
152
+ example in a Sinatra app:
153
+
154
+ get '/oauth/apps/new' do
155
+ @client = RockOAuth::Model::Client.new
156
+ erb :new_client
157
+ end
158
+
159
+ post '/oauth/apps' do
160
+ @client = RockOAuth::Model::Client.new(params)
161
+ @client.save ? erb(:show_client) : erb(:new_client)
162
+ end
163
+
164
+ Client applications must have a <tt>name</tt> and a <tt>redirect_uri</tt>:
165
+ provide fields for editing these but do not allow the other fields to be edited,
166
+ since they are the client's access credentials. When you've created the client,
167
+ you should show its details to the user registering the client: its
168
+ <tt>name</tt>, <tt>redirect_uri</tt>, <tt>client_id</tt> and
169
+ <tt>client_secret</tt> (the last two are generated for you).
170
+ <tt>client_secret</tt> is not stored in plain text so you can only read it when
171
+ you initially create the client object.
172
+
173
+
174
+ === OAuth request endpoint
175
+
176
+ This is a path that your application exposes in order for clients to communicate
177
+ with your application. It is also the page that the client will send users to
178
+ so they can authenticate and grant access. Many requests to this endpoint will
179
+ be protocol-level requests that do not involve the user, and
180
+ <tt>RockOAuth::Provider</tt> gives you a generic way to handle all that.
181
+
182
+ You should use this to get the right response, status code and headers to send
183
+ to the client. In the event that <tt>RockOAuth::Provider</tt> does not
184
+ provide a response, you should render a page that lets the user begin to
185
+ authenticate and grant access. This can happen in two cases:
186
+
187
+ * The client makes a valid Authorization request. In this case you should
188
+ display a login flow to the user so they can authenticate and grant access to
189
+ the client.
190
+ * The client makes an invalid Authorization request and the provider cannot
191
+ redirect back to the client. In this case you should display an error page
192
+ to the user, possibly including the value of <tt>@oauth2.error_description</tt>.
193
+
194
+ This endpoint must be accessible via GET and POST. In this example we will
195
+ expose the OAuth service through the path <tt>/oauth/authorize</tt>. We check if
196
+ there is a logged-in resource owner and give this to <tt>OAuth::Provider</tt>,
197
+ since we may be able to immediately redirect if the user has already authorized
198
+ the client:
199
+
200
+ [:get, :post].each do |method|
201
+ __send__ method, '/oauth/authorize' do
202
+ @owner = User.find_by_id(session[:user_id])
203
+ @oauth2 = RockOAuth::Provider.parse(@owner, env)
204
+
205
+ if @oauth2.redirect?
206
+ redirect @oauth2.redirect_uri, @oauth2.response_status
207
+ end
208
+
209
+ headers @oauth2.response_headers
210
+ status @oauth2.response_status
211
+
212
+ if body = @oauth2.response_body
213
+ body
214
+ elsif @oauth2.valid?
215
+ erb :login
216
+ else
217
+ erb :error
218
+ end
219
+ end
220
+ end
221
+
222
+ There is a set of parameters that you will need to hold on to for when your app
223
+ needs to redirect back to the client. You could store them in the session, or
224
+ pass them through forms as the user completes the flow. For example to embed
225
+ them in the login form, do this:
226
+
227
+ <% @oauth2.params.each do |key, value| %>
228
+ <input type="hidden" name="<%= key %>" value="<%= value %>">
229
+ <% end %>
230
+
231
+ You may also want to use scopes to provide granular access to your domain using
232
+ <i>scopes</i>. The <tt>@oauth2</tt> object exposes the scopes the client has
233
+ asked for so you can display them to the user:
234
+
235
+ <p>The application <%= @oauth2.client.name %> wants the following permissions:</p>
236
+
237
+ <ul>
238
+ <% @oauth2.scopes.each do |scope| %>
239
+ <li><%= PERMISSION_UI_STRINGS[scope] %></li>
240
+ <% end %>
241
+ </ul>
242
+
243
+ You can also use the method <tt>@oauth2.unauthorized_scopes</tt> to get the list
244
+ of scopes the user has not already granted to the client, in the case where the
245
+ client already has some authorization. If no prior authorization exists between
246
+ the user and the client, <tt>@oauth2.unauthorized_scopes</tt> just returns all
247
+ the scopes the client has asked for.
248
+
249
+
250
+ === Granting access to clients
251
+
252
+ Once the user has authenticated you should show them a page to let them grant
253
+ or deny access to the client application. This is straightforward; let's say the
254
+ user checks a box before posting a form to indicate their intent:
255
+
256
+ post '/oauth/allow' do
257
+ @user = User.find_by_id(session[:user_id])
258
+ @auth = RockOAuth::Provider::Authorization.new(@user, params)
259
+
260
+ if params['allow'] == '1'
261
+ @auth.grant_access!
262
+ else
263
+ @auth.deny_access!
264
+ end
265
+ redirect @auth.redirect_uri, @auth.response_status
266
+ end
267
+
268
+ After granting or denying access, we just redirect back to the client using a
269
+ URI that <tt>RockOAuth::Provider</tt> will provide for you.
270
+
271
+
272
+ === Using password credentials
273
+
274
+ If you like, OAuth lets you use a user's login credentials to authenticate with
275
+ a provider. In this case the client application must request these credentials
276
+ directly from the user and then post them to the exchange endpoint. On the
277
+ provider side you can handle this using the <tt>handle_passwords</tt> and
278
+ <tt>grant_access!</tt> API methods, for example:
279
+
280
+ RockOAuth::Provider.handle_passwords do |client, username, password, scopes|
281
+ user = User.find_by_username(username)
282
+ if user.authenticate?(password)
283
+ user.grant_access!(client, :scopes => scopes, :duration => 1.day)
284
+ else
285
+ nil
286
+ end
287
+ end
288
+
289
+ The block receives the <tt>Client</tt> making the request, the username,
290
+ password and a <tt>Set</tt> of the requested scopes. It must return
291
+ <tt>user.grant_access!(client)</tt> if you want to allow access, otherwise it
292
+ should return <tt>nil</tt>.
293
+
294
+
295
+ === Using assertions
296
+
297
+ Assertions provide a way to access your OAuth services using user credentials
298
+ from another service. When using assertions, the user will not authenticate on
299
+ your web site; the OAuth client will authenticate the user using some other
300
+ framework and obtain a token, then exchange this token for an access token on
301
+ your domain.
302
+
303
+ For example, a client application may let a user authenticate using Facebook,
304
+ so the application obtains a Facebook access token from the user. The client
305
+ would then pass this token to your OAuth endpoint and exchange it for an access
306
+ token from your site. You will typically create an account in your database to
307
+ represent this, then have that new account grant access to the client.
308
+
309
+ To use assertions, you must tell <tt>RockOAuth::Provider</tt> how to
310
+ handle assertions based on their type. An assertion type must be a valid URI.
311
+ For the Facebook example we'd do the following. The block yields the
312
+ <tt>Client</tt> object making the exchange request, the value of the assertion,
313
+ which in this example will be a Facebook access token, and a <tt>Set</tt> of
314
+ requested scopes.
315
+
316
+ RockOAuth::Provider.handle_assertions 'https://graph.facebook.com/me' do |client, assertion, scopes|
317
+ facebook = URI.parse('https://graph.facebook.com/me?access_token=' + assertion)
318
+ response = Net::HTTP.get_response(facebook)
319
+
320
+ user_data = JSON.parse(response.body)
321
+ account = User.from_facebook_data(user_data)
322
+
323
+ account.grant_access!(client, :scopes => scopes)
324
+ end
325
+
326
+ This code should run when your app boots, not during a request handler - think
327
+ of it as configuration for <tt>RockOAuth::Provider</tt>. The framework
328
+ will invoke it when a client attempts to use assertions with your OAuth endpoint.
329
+
330
+ The final call in your handler should be to <tt>grant_access!</tt>; this returns
331
+ an <tt>Authorization</tt> object that the framework then uses to complete the
332
+ response to the client. If you want to deny the request for whatever reason, the
333
+ block must return <tt>nil</tt>. If a client tries to use an assertion type you
334
+ have no handler for, the client will get an error response.
335
+
336
+
337
+ === Protecting resources with access tokens
338
+
339
+ To protect the user's resources you need to check for access tokens. This is
340
+ simple, for example a call to get a user's notes:
341
+
342
+ get '/user/:username/notes' do
343
+ user = User.find_by_username(params[:username])
344
+ token = RockOAuth::Provider.access_token(user, ['read_notes'], env)
345
+
346
+ headers token.response_headers
347
+ status token.response_status
348
+
349
+ if token.valid?
350
+ JSON.unparse('notes' => user.notes)
351
+ else
352
+ JSON.unparse('error' => 'No notes for you!')
353
+ end
354
+ end
355
+
356
+ <tt>RockOAuth::Provider.access_token()</tt> takes a
357
+ <tt>ResourceOwner</tt>, a list of scopes required to access the resource, and a
358
+ request environment object. If the token was not granted for the required scopes,
359
+ has expired or is simply invalid, headers and a status code are set to indicate
360
+ this to the client. <tt>token.valid?</tt> is the call you should use to
361
+ determine whether to serve the request or not.
362
+
363
+ It is also common to provide a dynamic resource for getting some basic data
364
+ about a user by supplying their access token. This can be done by passing
365
+ <tt>:implicit</tt> as the resource owner:
366
+
367
+ get '/me' do
368
+ token = RockOAuth::Provider.access_token(:implicit, [], env)
369
+ if token.valid?
370
+ JSON.unparse('username' => token.owner.username)
371
+ else
372
+ JSON.unparse('error' => 'Keep out!')
373
+ end
374
+ end
375
+
376
+ <tt>token.owner</tt> returns the <tt>ResourceOwner</tt> that issued the token.
377
+ A token represents the fact that a single owner gave a single client a set of
378
+ permissions.
379
+
380
+
381
+ == License
382
+
383
+ Copyright (c) 2014 Rocketmade.com
384
+
385
+ Permission is hereby granted, free of charge, to any person obtaining a copy
386
+ of this software and associated documentation files (the "Software"), to deal
387
+ in the Software without restriction, including without limitation the rights
388
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
389
+ copies of the Software, and to permit persons to whom the Software is
390
+ furnished to do so, subject to the following conditions:
391
+
392
+ The above copyright notice and this permission notice shall be included in
393
+ all copies or substantial portions of the Software.
394
+
395
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
396
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
397
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
398
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
399
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
400
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
401
+ THE SOFTWARE.
402
+
403
+
404
+ Copyright (c) 2010-2012 Songkick.com
405
+
406
+ Permission is hereby granted, free of charge, to any person obtaining a copy
407
+ of this software and associated documentation files (the "Software"), to deal
408
+ in the Software without restriction, including without limitation the rights
409
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
410
+ copies of the Software, and to permit persons to whom the Software is
411
+ furnished to do so, subject to the following conditions:
412
+
413
+ The above copyright notice and this permission notice shall be included in
414
+ all copies or substantial portions of the Software.
415
+
416
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
417
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
418
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
419
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
420
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
421
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
422
+ THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ = OAuth2::Provider example app
2
+
3
+ To get up and running:
4
+
5
+ # in parent directory
6
+ bundle install
7
+
8
+ cd example/
9
+ ruby schema.rb
10
+ rackup config.ru
11
+
@@ -0,0 +1,158 @@
1
+ dir = File.expand_path('..', __FILE__)
2
+ require dir + '/environment'
3
+
4
+ require 'sinatra'
5
+ require 'json'
6
+
7
+ set :static, true
8
+ set :root, dir
9
+ enable :sessions
10
+
11
+ PERMISSIONS = {
12
+ 'read_notes' => 'Read all your notes'
13
+ }
14
+
15
+ ERROR_RESPONSE = JSON.unparse('error' => 'No soup for you!')
16
+
17
+ get('/') { erb(:home) }
18
+
19
+
20
+ get '/users/new' do
21
+ @user = User.new
22
+ erb :new_user
23
+ end
24
+
25
+ post '/users/create' do
26
+ @user = User.create(params)
27
+ if @user.save
28
+ erb :create_user
29
+ else
30
+ erb :new_user
31
+ end
32
+ end
33
+
34
+ #================================================================
35
+ # Register applications
36
+
37
+ get '/oauth/apps/new' do
38
+ @client = RockOAuth::Model::Client.new
39
+ erb :new_client
40
+ end
41
+
42
+ post '/oauth/apps' do
43
+ @client = RockOAuth::Model::Client.new(params)
44
+ if @client.save
45
+ session[:client_secret] = @client.client_secret
46
+ redirect("/oauth/apps/#{@client.id}")
47
+ else
48
+ erb :new_client
49
+ end
50
+ end
51
+
52
+ get '/oauth/apps/:id' do
53
+ @client = RockOAuth::Model::Client.find_by_id(params[:id])
54
+ @client_secret = session[:client_secret]
55
+ erb :show_client
56
+ end
57
+
58
+
59
+ #================================================================
60
+ # OAuth 2.0 flow
61
+
62
+ # Initial request exmample:
63
+ # /oauth/authorize?response_type=token&client_id=7uljxxdgsksmecn5cycvug46v&redirect_uri=http%3A%2F%2Fexample.com%2Fcb&scope=read_notes
64
+ [:get, :post].each do |method|
65
+ __send__ method, '/oauth/authorize' do
66
+ @user = User.find_by_id(session[:user_id])
67
+ @oauth2 = RockOAuth::Provider.parse(@user, env)
68
+
69
+ if @oauth2.redirect?
70
+ redirect @oauth2.redirect_uri, @oauth2.response_status
71
+ end
72
+
73
+ headers @oauth2.response_headers
74
+ status @oauth2.response_status
75
+
76
+ if body = @oauth2.response_body
77
+ body
78
+ elsif @oauth2.valid?
79
+ erb :login
80
+ else
81
+ erb :error
82
+ end
83
+ end
84
+ end
85
+
86
+ post '/login' do
87
+ @user = User.find_by_username(params[:username])
88
+ @oauth2 = RockOAuth::Provider.parse(@user, env)
89
+ session[:user_id] = @user.id
90
+ erb(@user ? :authorize : :login)
91
+ end
92
+
93
+ post '/oauth/allow' do
94
+ @user = User.find_by_id(session[:user_id])
95
+ @auth = RockOAuth::Provider::Authorization.new(@user, params)
96
+ if params['allow'] == '1'
97
+ @auth.grant_access!
98
+ else
99
+ @auth.deny_access!
100
+ end
101
+ redirect @auth.redirect_uri, @auth.response_status
102
+ end
103
+
104
+ #================================================================
105
+ # Domain API
106
+
107
+ get '/me' do
108
+ authorization = RockOAuth::Provider.access_token(:implicit, [], env)
109
+ headers authorization.response_headers
110
+ status authorization.response_status
111
+
112
+ if authorization.valid?
113
+ user = authorization.owner
114
+ JSON.unparse('username' => user.username)
115
+ else
116
+ ERROR_RESPONSE
117
+ end
118
+ end
119
+
120
+ get '/users/:username/notes' do
121
+ verify_access :read_notes do |user|
122
+ notes = user.notes.map do |n|
123
+ {:note_id => n.id, :url => "#{host}/users/#{user.username}/notes/#{n.id}"}
124
+ end
125
+ JSON.unparse(:notes => notes)
126
+ end
127
+ end
128
+
129
+ get '/users/:username/notes/:note_id' do
130
+ verify_access :read_notes do |user|
131
+ note = user.notes.find_by_id(params[:note_id])
132
+ note ? note.to_json : JSON.unparse(:error => 'No such note')
133
+ end
134
+ end
135
+
136
+
137
+
138
+ helpers do
139
+ #================================================================
140
+ # Check for OAuth access before rendering a resource
141
+ def verify_access(scope)
142
+ user = User.find_by_username(params[:username])
143
+ token = RockOAuth::Provider.access_token(user, [scope.to_s], env)
144
+
145
+ headers token.response_headers
146
+ status token.response_status
147
+
148
+ return ERROR_RESPONSE unless token.valid?
149
+
150
+ yield user
151
+ end
152
+
153
+ #================================================================
154
+ # Return the full app domain
155
+ def host
156
+ request.scheme + '://' + request.host_with_port
157
+ end
158
+ end
data/example/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require File.expand_path('../application', __FILE__)
2
+ run Sinatra::Application
3
+
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'active_record'
5
+ require 'rockoauth/provider'
6
+ RockOAuth::Provider.realm = 'Notes App'
7
+
8
+ dir = File.expand_path('..', __FILE__)
9
+ require dir + '/models/connection'
10
+ require dir + '/models/user'
11
+ require dir + '/models/note'
@@ -0,0 +1,9 @@
1
+ require 'fileutils'
2
+
3
+ dbfile = File.expand_path('../../db/notes.sqlite3', __FILE__)
4
+ FileUtils.mkdir_p(File.dirname(dbfile))
5
+
6
+ ActiveRecord::Base.establish_connection(
7
+ :adapter => 'sqlite3',
8
+ :database => dbfile)
9
+
@@ -0,0 +1,4 @@
1
+ class Note < ActiveRecord::Base
2
+ belongs_to :user
3
+ end
4
+
@@ -0,0 +1,5 @@
1
+ class User < ActiveRecord::Base
2
+ include RockOAuth::Model::ResourceOwner
3
+ include RockOAuth::Model::ClientOwner
4
+ has_many :notes
5
+ end