rockoauth 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 (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