rockoauth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.txt +5 -0
- data/README.rdoc +422 -0
- data/example/README.rdoc +11 -0
- data/example/application.rb +158 -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 +5 -0
- data/example/public/style.css +78 -0
- data/example/schema.rb +22 -0
- data/example/views/authorize.erb +28 -0
- data/example/views/create_user.erb +3 -0
- data/example/views/error.erb +6 -0
- data/example/views/home.erb +24 -0
- data/example/views/layout.erb +24 -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/rockoauth/model/authorization.rb +132 -0
- data/lib/rockoauth/model/client.rb +54 -0
- data/lib/rockoauth/model/client_owner.rb +13 -0
- data/lib/rockoauth/model/hashing.rb +26 -0
- data/lib/rockoauth/model/helpers.rb +14 -0
- data/lib/rockoauth/model/resource_owner.rb +22 -0
- data/lib/rockoauth/model.rb +38 -0
- data/lib/rockoauth/provider/access_token.rb +70 -0
- data/lib/rockoauth/provider/authorization.rb +185 -0
- data/lib/rockoauth/provider/error.rb +19 -0
- data/lib/rockoauth/provider/exchange.rb +225 -0
- data/lib/rockoauth/provider.rb +133 -0
- data/lib/rockoauth/router.rb +75 -0
- data/lib/rockoauth/schema/20120828112156_rockoauth_schema_original_schema.rb +35 -0
- data/lib/rockoauth/schema/20121024180930_rockoauth_schema_add_authorization_index.rb +13 -0
- data/lib/rockoauth/schema/20121025180447_rockoauth_schema_add_unique_indexes.rb +31 -0
- data/lib/rockoauth/schema.rb +25 -0
- data/lib/rockoauth.rb +1 -0
- data/spec/factories.rb +20 -0
- data/spec/request_helpers.rb +62 -0
- data/spec/rockoauth/model/authorization_spec.rb +237 -0
- data/spec/rockoauth/model/client_spec.rb +44 -0
- data/spec/rockoauth/model/helpers_spec.rb +25 -0
- data/spec/rockoauth/model/resource_owner_spec.rb +87 -0
- data/spec/rockoauth/provider/access_token_spec.rb +138 -0
- data/spec/rockoauth/provider/authorization_spec.rb +356 -0
- data/spec/rockoauth/provider/exchange_spec.rb +361 -0
- data/spec/rockoauth/provider_spec.rb +560 -0
- data/spec/spec_helper.rb +80 -0
- data/spec/test_app/helper.rb +36 -0
- data/spec/test_app/provider/application.rb +67 -0
- data/spec/test_app/provider/views/authorize.erb +19 -0
- 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
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.
|
data/example/README.rdoc
ADDED
@@ -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,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'
|