oauth2_provider 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. data/MIT-LICENSE.txt +21 -0
  2. data/README.textile +271 -0
  3. data/app/controllers/oauth2/provider/oauth_authorize_controller.rb +65 -0
  4. data/app/controllers/oauth2/provider/oauth_clients_controller.rb +51 -0
  5. data/app/controllers/oauth2/provider/oauth_token_controller.rb +56 -0
  6. data/app/controllers/oauth2/provider/oauth_user_tokens_controller.rb +26 -0
  7. data/app/models/oauth2/provider/oauth_authorization.rb +31 -0
  8. data/app/models/oauth2/provider/oauth_client.rb +31 -0
  9. data/app/models/oauth2/provider/oauth_token.rb +35 -0
  10. data/app/views/oauth2/provider/layouts/oauth_clients.html.erb +17 -0
  11. data/app/views/oauth2/provider/oauth_authorize/index.html.erb +8 -0
  12. data/app/views/oauth2/provider/oauth_clients/edit.html.erb +20 -0
  13. data/app/views/oauth2/provider/oauth_clients/index.html.erb +26 -0
  14. data/app/views/oauth2/provider/oauth_clients/new.html.erb +19 -0
  15. data/app/views/oauth2/provider/oauth_clients/show.html.erb +22 -0
  16. data/app/views/oauth2/provider/oauth_user_tokens/index.html.erb +14 -0
  17. data/config/routes.rb +11 -0
  18. data/generators/oauth2_provider/oauth2_provider_generator.rb +25 -0
  19. data/generators/oauth2_provider/templates/config/initializers/oauth2_provider.rb +14 -0
  20. data/generators/oauth2_provider/templates/db/migrate/create_oauth_authorizations.rb +18 -0
  21. data/generators/oauth2_provider/templates/db/migrate/create_oauth_clients.rb +16 -0
  22. data/generators/oauth2_provider/templates/db/migrate/create_oauth_tokens.rb +19 -0
  23. data/init.rb +3 -0
  24. data/lib/oauth2/provider/application_controller_methods.rb +52 -0
  25. data/lib/oauth2/provider/clock.rb +20 -0
  26. data/tasks/gem.rake +33 -0
  27. metadata +81 -0
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 ThoughtWorks, Inc. (http://thoughtworks.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,271 @@
1
+ h1. Introduction
2
+
3
+ This plugin implements v09 of the OAuth2 draft spec "http://tools.ietf.org/html/draft-ietf-oauth-v2-09":http://tools.ietf.org/html/draft-ietf-oauth-v2-09.
4
+ The latest version of the spec is available at "http://tools.ietf.org/html/draft-ietf-oauth-v2":http://tools.ietf.org/html/draft-ietf-oauth-v2
5
+
6
+ Currently only the web-server profile "http://tools.ietf.org/html/draft-ietf-oauth-v2-09#section-1.4.1":http://tools.ietf.org/html/draft-ietf-oauth-v2-09#section-1.4.1 is supported.
7
+
8
+ h1. What is OAuth?
9
+
10
+ OAuth is an open-source specification for building a framework for allowing a third-party app (the "client") to access protected resources from another application (the "provider," or "resource owner") at the request of a "user" of the client app. Oauth allows the user to enter his user credentials (ex. username and password) only to the provider app, which then grants the client app permission to view the protected resources on behalf of the user.
11
+
12
+ A very good overview of the basic OAuth workflow is "here":http://hueniverse.com/2007/10/beginners-guide-to-oauth-part-ii-protocol-workflow/.
13
+
14
+ Common terms:
15
+
16
+ * Provider/Resource Owner - the app that hosts the protected resource. A real world example is Twitter which uses OAuth as the protocol for all its clients.
17
+ * Client - the app that requests to see the resource data on behalf of the user. Any Twitter client that shows tweets is an example of this.
18
+ * User/end user - the entity who initiates the OAuth flow to allow the client to access protected data from the provider.
19
+ * Client id/client secret - Often, provider apps will maintain a list of clients that are allowed to access their data. Client apps can be identified in a number of ways, including with an id and a secret.
20
+
21
+ h1. What's Included
22
+
23
+ * The OAuth 2.0 (v9) plugin (RAILS_ROOT/vendor/plugins/oauth2_provider)
24
+ * Units and functional tests (RAILS_ROOT/test)
25
+ * A sample host Rails application (RAILS_ROOT)
26
+ ** Create sample app user at /users/new
27
+ ** Create sample app OAuth client at /oauth/clients
28
+ ** Access OAuth-allowed protected resource /protected_resource
29
+ ** List and revoke a user's tokens at /oauth/user_tokens
30
+
31
+ h1. Assumptions
32
+
33
+ Usage of this plugin assumes that you already have a RAILS app that has unique users who access user-specific data by logging in, or some other method of user authentication.
34
+
35
+ h1. Supported Features
36
+
37
+ * Web server flow as described at "http://tools.ietf.org/html/draft-ietf-oauth-v2-09#section-1.4.1":http://tools.ietf.org/html/draft-ietf-oauth-v2-09#section-1.4.1
38
+ * Endpoint for clients to request authorization 'code'
39
+ * Endpoint to request an access token using the authorization 'code'
40
+ * Admin screens for:
41
+ * End users to manage/revoke access tokens given out to 3rd party OAuth2 clients
42
+ * Admins to manage OAuth2 clients
43
+
44
+ h1. Available Endpoint URLs
45
+
46
+ See config/routes.rb in the plugin for more details.
47
+
48
+ *API only URLs*
49
+ * /oauth/authorize (Oauth2::Provider::OauthAuthorizeController) - used by the user-agent (browser) to request an authorization 'code'
50
+ * /oauth/token (Oauth2::Provider::OauthTokenController) - used by the OAuth2 client to request access token to access protected resources
51
+
52
+ *Accessed from the browser*
53
+ * /oauth/user_tokens (Oauth2::Provider::OauthUserTokensController) - used by end users to view and revoke access to 3rd party OAuth2 clients.
54
+ * /oauth/clients (Oauth2::Provider::OauthClientsController) - to manage oauth clients (should be available only to admins)
55
+
56
+ It is the responsibility of the host application to avoid routing conflicts. The simplest thing to do is avoid defining any paths starting with /oauth
57
+
58
+ h1. Installation
59
+
60
+ Copy the plugin into your rails application's vendor/plugins directory.
61
+
62
+ Execute the configuration script from your RAILS_ROOT:
63
+
64
+ $ ./script/generate oauth2_provider
65
+
66
+ This will create:
67
+ * config/initializers/oauth2_provider.rb
68
+ * db/migrate/TIMESTAMP_create_oauth_authorizations.rb
69
+ * db/migrate/TIMESTAMP_create_oauth_clients.rb
70
+ * db/migrate/TIMESTAMP_create_oauth_tokens.rb
71
+
72
+ h2. Configuration
73
+
74
+ h3. Host Application
75
+
76
+ h4. Rails initializer
77
+
78
+ Edit the file config/initializers/oauth2_provider.rb in which you
79
+ * must call filter skipping methods on OauthTokenController, ensuring any authentication filters to not run for this controllers actions
80
+ * must setup authorization for OauthClientsController, limiting access to only application administrators
81
+ * must setup authorization for OauthUserTokensController, limiting access to logged in users (this step might not actually require you to write code, we're just putting it here to make sure you consider this issue)
82
+ * might call filter skipping or similar methods on the other provided controllers should it be necessary for them to run
83
+
84
+ A sample initializer:
85
+
86
+ <pre>
87
+ module Oauth2
88
+ module Provider
89
+
90
+ # make sure no authentication for OauthTokenController
91
+ OauthTokenController.skip_before_filter(:login_required)
92
+
93
+ # use host app's custom authorization filter to protect OauthClientsController
94
+ OauthClientsController.before_filter(:ensure_admin_user)
95
+
96
+ end
97
+ end
98
+
99
+ </pre>
100
+
101
+
102
+ h4. ApplicationController
103
+
104
+ Make the following changes:
105
+ * Include Oauth2::Provider::ApplicationControllerMethods module
106
+ * Define a 'current_user_id_for_oauth' method that returns the id of the currently logged-in user, serving as a foreign-key to any tokens that are stored in the database.
107
+ * alias_method_chain 'user_id_for_oauth_access_token' into your authentication filter. This method is supplied by Oauth2::Provider::ApplicationControllerMethods and converts an Oauth access token passed in a request's header to a user_id known by the host application. This user_id corresponds to whatever your current_user_id method returns.
108
+
109
+ *Before*
110
+
111
+ <pre>
112
+ class ApplicationController < ActionController::Base
113
+
114
+ # the host application's authentication filter, you'll
115
+ # see in the 'After' section below that you can user
116
+ # alias_method_chain to merge OAuth 2.0 authentication
117
+ # with your current authentication filter.
118
+ before_filter :login_required
119
+
120
+ # this checks whether the user is logged in for purposes
121
+ # of an authentication filter. obviously, your host application
122
+ # will have very different code than this. this example is
123
+ # pulled from the sample host application with which the plugin ships.
124
+ def login_required
125
+ current_user_id = session[:user_id]
126
+ if current_user_id
127
+ User.current = User.new(current_user_id)
128
+ else
129
+ raise "Lack of rights!"
130
+ end
131
+ end
132
+
133
+ end
134
+ </pre>
135
+
136
+ *After*
137
+
138
+ <pre>
139
+ class ApplicationController < ActionController::Base
140
+
141
+ # the host application's authentication filter
142
+ before_filter :login_required
143
+
144
+ # include Oauth2::Provider::ApplicationControllerMethods
145
+ include Oauth2::Provider::ApplicationControllerMethods
146
+
147
+ # this checks whether the user is logged in for purposes
148
+ # of an authentication filter. obviously, your host application
149
+ # will have very different code than this. this example is
150
+ # pulled from the sample host application with which the plugin ships.
151
+ def login_required
152
+ current_user_id = session[:user_id]
153
+ if current_user_id
154
+ User.current = User.new(current_user_id)
155
+ else
156
+ raise "Lack of rights!"
157
+ end
158
+ end
159
+
160
+ # required by the OAuth plugin to figure out the currently logged in user
161
+ # must be a string representation of the user.
162
+ # A 'username', 'email' or a db primary key are good candidates.
163
+ protected def current_user_id_for_oauth
164
+ super
165
+ end
166
+
167
+ # use alias_method_chain to wrap your existing authentication filter
168
+ # with behavior that will use any valid passed Oauth access token header,
169
+ # falling back to your standard filter if the request is not an Oauth request.
170
+ #
171
+ # clearly, your host application will have different code than
172
+ # this example, with different authentication models and different filter
173
+ # method names.
174
+ #
175
+ # As for this example, OAuth2::Provider::ApplicationControllerMethods supplies
176
+ # a user_id_for_oauth_access_token to lookup the user_id for a given access token.
177
+ # The host app must then use that
178
+ # user_id to setup the user session, do any authorization checks, etc. in this
179
+ # example (from the included sample host app) we simply store the user_id in
180
+ # the user's session.
181
+ #
182
+ # Note that user_id_for_oauth_access_token returns nil if an action
183
+ # has not been enabled for Oauth (see below).
184
+ def login_required_with_oauth
185
+ if user_id = self.user_id_for_oauth_access_token
186
+ session[:user_id] = user_id
187
+ elsif looks_like_oauth_request?
188
+ render :text => "Denied!", :status => :unauthorized
189
+ else
190
+ login_required_without_oauth
191
+ end
192
+ end
193
+ alias_method_chain :login_required, :oauth
194
+
195
+ end
196
+ </pre>
197
+
198
+ h4. Oauth enable particular controller actions
199
+
200
+ By default, no action supports OAuth. That is, if you would like for OAuth to work for any particular action you must declare that in the controller. Below is a simple example where a concrete controller definition specifies where Oauth is allowed. If you look at the plugin source you'll see that oauth_allowed can also be passed a block, returning true or false, for more sophisticated implementations.
201
+
202
+ <pre>
203
+ class ProtectedResourceController < ApplicationController
204
+
205
+ # Supported options are:
206
+ # :only => [:oauth_protected_action...]
207
+ # :except => [:oauth_unprotected_action...]
208
+ # If no options are specified, defaults to oauth for all actions
209
+ oauth_allowed :only => :index
210
+
211
+ def index
212
+ render :text => "current user is #{current_user.email}"
213
+ end
214
+
215
+ def no_oauth_here
216
+ render :text => "this content not available via Oauth"
217
+ end
218
+
219
+ end
220
+ </pre>
221
+
222
+ h3. Database
223
+
224
+ This plugin stores OAuth2 client, authorization codes, and access token into a DB table. It is therefore required that a host app create the necessary ActiveRecord migrations in their application.
225
+
226
+ * In your RAILS_ROOT, run rake db:migrate, this will run all the migrations that were created as when you executed the oauth2_provider generator. You can't do this step until you do the initializers file.
227
+
228
+
229
+ h1. Verify plugin working correctly in your application
230
+
231
+ * OAuth enable the actions for which you wish to allow OAuth as an authentication means
232
+ * Setup a new OAuth client at /oauth/clients
233
+ * Open /oauth/authorize?redirect_uri=REDIRECT_URI&client_id=CLIENT_ID&response_type=code in a browser. You should see a form asking you to authorize the client to access the host application on your behalf. Check the tickbox and submit.
234
+ * Your browser is redirected to your redirect URI (hint: use example.com for this test) and you should now see your authorization code as the "code" parameter. Save this code, you will need it for the next step.
235
+ * For this step you'll need to use curl as you need to perform a POST and there is no form provided by the plugin. Use curl to POST to /oauth/token, passing the following key/value pairs as form data: code=AUTHORIZATION_CODE&grant_type=authorization-code&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=REDIRECT_URI
236
+ * The response to the above POST will be JSON containing your access token, and its expiration time in seconds.
237
+
238
+ <pre>{:access_token => ACCESS_TOKEN, :expires_in => ACCESS_TOKEN_EXPIRES_IN, :refresh_token => REFRESH_TOKEN}</pre>
239
+
240
+ * You can now use the ACCESS_TOKEN to access the protected resource. This would require the 'Authorization' HTTP header (NOTE that the quotes are required around the ACCESS_TOKEN)
241
+
242
+ <pre>Authorization: Token token="ACCESS_TOKEN"</pre>
243
+
244
+ * You should expect to see the contents of the protected resource now.
245
+
246
+
247
+ h1. License (Stuff that lawyers make us say)
248
+
249
+ OAuth2 Provider Generator is MIT Licensed.
250
+
251
+ The MIT License
252
+
253
+ Copyright © 2010 ThoughtWorks, Inc. (http://thoughtworks.com)
254
+
255
+ Permission is hereby granted, free of charge, to any person obtaining a copy
256
+ of this software and associated documentation files (the “Software”), to deal
257
+ in the Software without restriction, including without limitation the rights
258
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
259
+ copies of the Software, and to permit persons to whom the Software is
260
+ furnished to do so, subject to the following conditions:
261
+
262
+ The above copyright notice and this permission notice shall be included in
263
+ all copies or substantial portions of the Software.
264
+
265
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
266
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
267
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
268
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
269
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
270
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
271
+ THE SOFTWARE.
@@ -0,0 +1,65 @@
1
+ module Oauth2
2
+ module Provider
3
+ class OauthAuthorizeController < ::ApplicationController
4
+
5
+ def index
6
+ return unless validate_params
7
+ end
8
+
9
+ def authorize
10
+ return unless validate_params
11
+
12
+ unless params[:authorize] == '1'
13
+ redirect_to "#{params[:redirect_uri]}?error=access-denied"
14
+ return
15
+ end
16
+
17
+ authorization = @client.create_authorization_for_user_id(current_user_id_for_oauth)
18
+ state_param = if params[:state].blank?
19
+ ""
20
+ else
21
+ "&state=#{CGI.escape(params[:state])}"
22
+ end
23
+
24
+ redirect_to "#{params[:redirect_uri]}?code=#{authorization.code}&expires_in=#{authorization.expires_in}#{state_param}"
25
+ end
26
+
27
+ private
28
+
29
+ # TODO: support 'code', 'token', 'code-and-token'
30
+ VALID_RESPONSE_TYPES = ['code']
31
+
32
+ def validate_params
33
+ if params[:client_id].blank? || params[:response_type].blank?
34
+ redirect_to "#{params[:redirect_uri]}?error=invalid-request"
35
+ return false
36
+ end
37
+
38
+ unless VALID_RESPONSE_TYPES.include?(params[:response_type])
39
+ redirect_to "#{params[:redirect_uri]}?error=unsupported-response-type"
40
+ return
41
+ end
42
+
43
+ if params[:redirect_uri].blank?
44
+ render :text => "You did not specify the 'redirect_uri' parameter!", :status => :bad_request
45
+ return false
46
+ end
47
+
48
+ @client = OauthClient.find_by_client_id(params[:client_id])
49
+
50
+ if @client.nil?
51
+ redirect_to "#{params[:redirect_uri]}?error=invalid-client-id"
52
+ return false
53
+ end
54
+
55
+ if @client.redirect_uri != params[:redirect_uri]
56
+ redirect_to "#{params[:redirect_uri]}?error=redirect-uri-mismatch"
57
+ return false
58
+ end
59
+
60
+ true
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,51 @@
1
+ module Oauth2
2
+ module Provider
3
+ class OauthClientsController < ApplicationController
4
+
5
+ def index
6
+ @oauth_clients = OauthClient.all
7
+ end
8
+
9
+ def show
10
+ @oauth_client = OauthClient.find(params[:id])
11
+ end
12
+
13
+ def new
14
+ @oauth_client = OauthClient.new
15
+ end
16
+
17
+ def edit
18
+ @oauth_client = OauthClient.find(params[:id])
19
+ end
20
+
21
+ def create
22
+ @oauth_client = OauthClient.new(params[:oauth_client])
23
+
24
+ if @oauth_client.save
25
+ flash[:notice] = 'OauthClient was successfully created.'
26
+ redirect_to(@oauth_client)
27
+ else
28
+ render :action => "new"
29
+ end
30
+ end
31
+
32
+ def update
33
+ @oauth_client = OauthClient.find(params[:id])
34
+
35
+ if @oauth_client.update_attributes(params[:oauth_client])
36
+ flash[:notice] = 'OauthClient was successfully updated.'
37
+ redirect_to(@oauth_client)
38
+ else
39
+ render :action => "edit"
40
+ end
41
+ end
42
+
43
+ def destroy
44
+ @oauth_client = OauthClient.find(params[:id])
45
+ @oauth_client.destroy
46
+
47
+ redirect_to(oauth_clients_url)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
1
+ module Oauth2
2
+ module Provider
3
+ class OauthTokenController < ApplicationController
4
+
5
+ def get_token
6
+
7
+ authorization = OauthAuthorization.find_by_code(params[:code])
8
+ authorization.delete unless authorization.nil?
9
+
10
+ original_token = OauthToken.find_by_refresh_token(params[:refresh_token])
11
+ original_token.delete unless original_token.nil?
12
+
13
+ unless ['authorization-code', 'refresh-token'].include?(params[:grant_type])
14
+ render_error('unsupported-grant-type')
15
+ return
16
+ end
17
+
18
+ client = OauthClient.find_by_client_id_and_client_secret(
19
+ params[:client_id], params[:client_secret]
20
+ )
21
+
22
+ if client.nil?
23
+ render_error('invalid-client-credentials')
24
+ return
25
+ end
26
+
27
+ if client.redirect_uri != params[:redirect_uri]
28
+ render_error('invalid-grant')
29
+ return
30
+ end
31
+
32
+ if params[:grant_type] == 'authorization-code'
33
+ if authorization.nil? || authorization.expired? || authorization.oauth_client != client
34
+ render_error('invalid-grant')
35
+ return
36
+ end
37
+ token = authorization.generate_access_token
38
+ else # refresh-token
39
+ if original_token.nil? || original_token.oauth_client != client
40
+ render_error('invalid-grant')
41
+ return
42
+ end
43
+ token = original_token.refresh
44
+ end
45
+
46
+ render :content_type => 'application/json', :text => token.access_token_attributes.to_json
47
+ end
48
+
49
+ private
50
+ def render_error(error_code)
51
+ render :status => :bad_request, :json => {:error => error_code}.to_json
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ module Oauth2
2
+ module Provider
3
+ class OauthUserTokensController < ApplicationController
4
+
5
+ def index
6
+ @tokens = OauthToken.find_all_by_user_id(current_user_id_for_oauth)
7
+ end
8
+
9
+ def revoke
10
+ token = OauthToken.find_by_id(params[:token_id])
11
+ if token.nil?
12
+ render :text => "User not authorized to perform this action!", :status => :bad_request
13
+ return
14
+ end
15
+ if token.user_id != current_user_id_for_oauth
16
+ render :text => "User not authorized to perform this action!", :status => :bad_request
17
+ return
18
+ end
19
+
20
+ token.delete
21
+ redirect_to :action => :index
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ module Oauth2
2
+ module Provider
3
+ class OauthAuthorization < ::ActiveRecord::Base
4
+
5
+ belongs_to :oauth_client, :class_name => "Oauth2::Provider::OauthClient"
6
+
7
+ EXPIRY_TIME = 1.hour
8
+
9
+ def generate_access_token
10
+ token = oauth_client.create_token_for_user_id(user_id)
11
+ self.delete
12
+ token
13
+ end
14
+
15
+ def expires_in
16
+ (Time.at(expires_at.to_i) - Clock.now).to_i
17
+ end
18
+
19
+ def expired?
20
+ expires_in <= 0
21
+ end
22
+
23
+ protected
24
+ def before_create
25
+ self.expires_at = Clock.now + EXPIRY_TIME
26
+ self.code = ActiveSupport::SecureRandom.hex(32)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Oauth2
2
+ module Provider
3
+ class OauthClient < ActiveRecord::Base
4
+
5
+ validates_presence_of :name, :redirect_uri
6
+ before_create :generate_keys
7
+ has_many :oauth_tokens, :class_name => "Oauth2::Provider::OauthToken", :dependent => :delete_all
8
+ has_many :oauth_authorizations, :class_name => "Oauth2::Provider::OauthAuthorization", :dependent => :delete_all
9
+ validates_format_of :redirect_uri, :with => Regexp.new("^(https|http)://.+$")
10
+
11
+ def create_token_for_user_id(user_id)
12
+ oauth_tokens.create!(:user_id => user_id)
13
+ end
14
+
15
+ def create_authorization_for_user_id(user_id)
16
+ oauth_authorizations.create!(:user_id => user_id)
17
+ end
18
+
19
+ def self.model_name
20
+ ActiveSupport::ModelName.new('OauthClient')
21
+ end
22
+
23
+ private
24
+ def generate_keys
25
+ self.client_id = ActiveSupport::SecureRandom.hex(32)
26
+ self.client_secret = ActiveSupport::SecureRandom.hex(32)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ module Oauth2
2
+ module Provider
3
+ class OauthToken < ::ActiveRecord::Base
4
+
5
+ belongs_to :oauth_client, :class_name => "Oauth2::Provider::OauthClient"
6
+
7
+ EXPIRY_TIME = 90.days
8
+
9
+ def access_token_attributes
10
+ {:access_token => access_token, :expires_in => expires_in, :refresh_token => refresh_token}
11
+ end
12
+
13
+ def expires_in
14
+ (Time.at(expires_at.to_i) - Clock.now).to_i
15
+ end
16
+
17
+ def expired?
18
+ expires_in <= 0
19
+ end
20
+
21
+ def refresh
22
+ self.delete
23
+ oauth_client.create_token_for_user_id(user_id)
24
+ end
25
+
26
+ protected
27
+ def before_create
28
+ self.access_token = ActiveSupport::SecureRandom.hex(32)
29
+ self.expires_at = Clock.now + EXPIRY_TIME
30
+ self.refresh_token = ActiveSupport::SecureRandom.hex(32)
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
7
+ <title>OauthClients: <%= controller.action_name %></title>
8
+ <%= stylesheet_link_tag 'scaffold' %>
9
+ </head>
10
+ <body>
11
+ Currently Logged in: <%= current_user.email if logged_in? %>
12
+ <p style="color: green"><%= flash[:notice] %></p>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
@@ -0,0 +1,8 @@
1
+ <form action="<%= url_for(:action => :authorize) %>" method="post" id="oauth_authorize">
2
+ Do you wish to allow the service named '<%= @client.name %>' to access this application on your behalf? <input type="checkbox" name="authorize" id="authorize" value="1" />
3
+ <input type="hidden" name="client_id" id="client_id" value="<%= params[:client_id] %>" />
4
+ <input type="hidden" name="redirect_uri" id="redirect_uri" value="<%= params[:redirect_uri] %>" />
5
+ <input type="hidden" name="response_type" id="response_type" value="<%= params[:response_type] %>" />
6
+ <input type="hidden" name="state" id="state" value="<%= params[:state] %>" />
7
+ <input type="submit" value="Submit" />
8
+ </form>
@@ -0,0 +1,20 @@
1
+ <h1>Editing oauth_client</h1>
2
+
3
+ <% form_for(@oauth_client) do |f| %>
4
+ <%= f.error_messages %>
5
+
6
+ <p>
7
+ <%= f.label :name %><br />
8
+ <%= f.text_field :name %>
9
+ </p>
10
+ <p>
11
+ <%= f.label :redirect_uri %><br />
12
+ <%= f.text_field :redirect_uri %>
13
+ </p>
14
+ <p>
15
+ <%= f.submit 'Update' %>
16
+ </p>
17
+ <% end %>
18
+
19
+ <%= link_to 'Show', @oauth_client %> |
20
+ <%= link_to 'Back', oauth_clients_path %>
@@ -0,0 +1,26 @@
1
+ <h1>Listing oauth_clients</h1>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>Name</th>
6
+ <th>Client id</th>
7
+ <th>Client secret</th>
8
+ <th>Redirect URI</th>
9
+ </tr>
10
+
11
+ <% @oauth_clients.each do |oauth_client| %>
12
+ <tr>
13
+ <td><%=h oauth_client.name %></td>
14
+ <td><%=h oauth_client.client_id %></td>
15
+ <td><%=h oauth_client.client_secret %></td>
16
+ <td><%=h oauth_client.redirect_uri %></td>
17
+ <td><%= link_to 'Show', oauth_client %></td>
18
+ <td><%= link_to 'Edit', edit_oauth_client_path(oauth_client) %></td>
19
+ <td><%= link_to 'Destroy', oauth_client, :confirm => 'Are you sure?', :method => :delete %></td>
20
+ </tr>
21
+ <% end %>
22
+ </table>
23
+
24
+ <br />
25
+
26
+ <%= link_to 'New oauth_client', new_oauth_client_path %>
@@ -0,0 +1,19 @@
1
+ <h1>New oauth_client</h1>
2
+
3
+ <% form_for(@oauth_client) do |f| %>
4
+ <%= f.error_messages %>
5
+
6
+ <p>
7
+ <%= f.label :name %><br />
8
+ <%= f.text_field :name %>
9
+ </p>
10
+ <p>
11
+ <%= f.label :redirect_uri %><br />
12
+ <%= f.text_field :redirect_uri %>
13
+ </p>
14
+ <p>
15
+ <%= f.submit 'Create' %>
16
+ </p>
17
+ <% end %>
18
+
19
+ <%= link_to 'Back', oauth_clients_path %>
@@ -0,0 +1,22 @@
1
+ <p>
2
+ <b>Name:</b>
3
+ <%=h @oauth_client.name %>
4
+ </p>
5
+
6
+ <p>
7
+ <b>Client:</b>
8
+ <%=h @oauth_client.client_id %>
9
+ </p>
10
+
11
+ <p>
12
+ <b>Client secret:</b>
13
+ <%=h @oauth_client.client_secret %>
14
+ </p>
15
+
16
+ <p>
17
+ <b>Redirect URI:</b>
18
+ <%=h @oauth_client.redirect_uri %>
19
+ </p>
20
+
21
+ <%= link_to 'Edit', edit_oauth_client_path(@oauth_client) %> |
22
+ <%= link_to 'Back', oauth_clients_path %>
@@ -0,0 +1,14 @@
1
+ <table>
2
+ <tr>
3
+ <th>Client</th>
4
+ <th>Token</th>
5
+ <th>&nbsp;</th>
6
+ </tr>
7
+ <% @tokens.each do |token| -%>
8
+ <tr>
9
+ <td><%= token.oauth_client.name %></td>
10
+ <td><%= token.access_token %></td>
11
+ <td><%= link_to('Destroy', {:action => :revoke, :token_id => token.id, :controller => 'Oauth2::Provider::OauthUserTokens'}, {:confirm => 'Are you sure?', :method => :delete})%></td>
12
+ </tr>
13
+ <% end -%>
14
+ </table>
@@ -0,0 +1,11 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+
3
+ map.resources :oauth_clients, :controller => 'Oauth2::Provider::OauthClients', :as => 'oauth/clients'
4
+
5
+ map.connect '/oauth/authorize', :controller => 'Oauth2::Provider::OauthAuthorize', :action => :authorize, :conditions => {:method => :post}
6
+ map.connect '/oauth/authorize', :controller => 'Oauth2::Provider::OauthAuthorize', :action => :index, :conditions => {:method => :get}
7
+ map.connect '/oauth/token', :controller => 'Oauth2::Provider::OauthToken', :action => :get_token, :conditions => {:method => :post}
8
+
9
+ map.connect '/oauth/user_tokens/revoke/:token_id', :controller => 'Oauth2::Provider::OauthUserTokens', :action => :revoke, :conditions => {:method => :delete}
10
+ map.connect '/oauth/user_tokens', :controller => 'Oauth2::Provider::OauthUserTokens', :action => :index, :conditions => {:method => :get}
11
+ end
@@ -0,0 +1,25 @@
1
+ class Oauth2ProviderGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.template 'config/initializers/oauth2_provider.rb', "config/initializers/oauth2_provider.rb"
5
+
6
+ ['create_oauth_clients', 'create_oauth_tokens', 'create_oauth_authorizations'].each_with_index do |file_name, index|
7
+ m.template "db/migrate/#{file_name}.rb", "db/migrate/#{version_with_prefix(index)}_#{file_name}.rb", :migration_file_name => file_name
8
+ end
9
+
10
+ end
11
+ end
12
+
13
+ def after_generate
14
+ puts "*"*80
15
+ puts "Please edit the file 'config/initializers/oauth2_provider.rb' as per your needs!"
16
+ puts "The readme file in the plugin contains more information about configuration."
17
+ puts "*"*80
18
+ end
19
+
20
+ private
21
+ def version_with_prefix(prefix)
22
+ Time.now.utc.strftime("%Y%m%d%H%M%S") + "#{prefix}"
23
+ end
24
+
25
+ end
@@ -0,0 +1,14 @@
1
+ module Oauth2
2
+ module Provider
3
+
4
+ raise 'OAuth2 provider not configured yet!'
5
+ # please go through the readme and configure this file before you can use this plugin!
6
+
7
+ # make sure no authentication for OauthTokenController
8
+ OauthTokenController.skip_before_filter(:login_required)
9
+
10
+ # use host app's custom authorization filter to protect OauthClientsController
11
+ OauthClientsController.before_filter(:ensure_admin_user)
12
+
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ class CreateOauthAuthorizations < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :oauth_authorizations do |t|
4
+ t.string :user_id
5
+ t.integer :oauth_client_id
6
+ t.string :code
7
+ t.timestamp :expires_at
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ end
13
+
14
+ def self.down
15
+ drop_table :oauth_authorizations
16
+ end
17
+
18
+ end
@@ -0,0 +1,16 @@
1
+ class CreateOauthClients < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :oauth_clients do |t|
4
+ t.string :name
5
+ t.string :client_id
6
+ t.string :client_secret
7
+ t.string :redirect_uri
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :oauth_clients
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ class CreateOauthTokens < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :oauth_tokens do |t|
4
+ t.string :user_id
5
+ t.integer :oauth_client_id
6
+ t.string :access_token
7
+ t.string :refresh_token
8
+ t.timestamp :expires_at
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ end
14
+
15
+ def self.down
16
+ drop_table :oauth_tokens
17
+ end
18
+
19
+ end
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ if RAILS_ENV == 'development'
2
+ ActiveSupport::Dependencies.load_once_paths.reject!{|x| x =~ /^#{Regexp.escape(File.dirname(__FILE__))}/}
3
+ end
@@ -0,0 +1,52 @@
1
+ module Oauth2
2
+ module Provider
3
+ module ApplicationControllerMethods
4
+
5
+ def self.included(controller_class)
6
+ controller_class.cattr_accessor :oauth_options, :oauth_options_proc
7
+
8
+ def controller_class.oauth_allowed(options = {}, &block)
9
+ raise 'options cannot contain both :only and :except' if options[:only] && options[:except]
10
+
11
+ [:only, :except].each do |k|
12
+ if values = options[k]
13
+ options[k] = Array(values).map(&:to_s).to_set
14
+ end
15
+ end
16
+ self.oauth_options = options
17
+ self.oauth_options_proc = block
18
+ end
19
+
20
+ end
21
+
22
+ protected
23
+
24
+ def user_id_for_oauth_access_token
25
+ return nil unless oauth_allowed?
26
+ header_field = request.headers["Authorization"]
27
+
28
+ if header_field =~ /Token token="(.*)"/
29
+ token = OauthToken.find_by_access_token($1)
30
+ token.user_id if token
31
+ end
32
+ end
33
+
34
+ def looks_like_oauth_request?
35
+ header_field = request.headers["Authorization"]
36
+ header_field =~ /Token token="(.*)"/
37
+ end
38
+
39
+ def oauth_allowed?
40
+ if (oauth_options_proc && !oauth_options_proc.call(self))
41
+ false
42
+ else
43
+ return false if oauth_options.nil?
44
+ oauth_options.empty? ||
45
+ (oauth_options[:only] && oauth_options[:only].include?(action_name)) ||
46
+ (oauth_options[:except] && !oauth_options[:except].include?(action_name))
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ module Oauth2
2
+ module Provider
3
+
4
+ class Clock
5
+
6
+ def self.fake_now=(time_now)
7
+ @fake_now = time_now
8
+ end
9
+
10
+ def self.now
11
+ @fake_now || Time.now
12
+ end
13
+
14
+ def self.reset
15
+ @fake_now = nil
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ desc 'Create a the oauth2_provider gem'
5
+ task :gem do
6
+ cd File.join(File.expand_path(File.dirname(__FILE__)), '..') do
7
+
8
+ cp "#{RAILS_ROOT}/../README.textile", '.'
9
+ cp "#{RAILS_ROOT}/../MIT-LICENSE.txt", '.'
10
+
11
+ spec = Gem::Specification.new do |s|
12
+ s.name = "oauth2_provider"
13
+ s.version = "0.0.1"
14
+ s.author = "ThoughtWorks, Inc."
15
+ s.email = "ketan@thoughtworks.com"
16
+ s.homepage = "http://github.com/ThoughtWorksStudios/oauth2_provider"
17
+ s.platform = Gem::Platform::RUBY
18
+ s.summary = "A Rails plugin to OAuth v2.0 enable your rails application"
19
+ s.description = "A Rails plugin to OAuth v2.0 enable your rails application"
20
+ s.files = Dir["**/*.*"]
21
+ s.has_rdoc = false
22
+ s.extra_rdoc_files = ["README.textile", "MIT-LICENSE.txt"]
23
+ end
24
+ File.open("oauth2_provider.gemspec", "w") { |f| f << spec.to_ruby }
25
+ rm_rf "#{RAILS_ROOT}/pkg"
26
+ sh "gem build #{spec.name}.gemspec"
27
+ mkdir "#{RAILS_ROOT}/pkg"
28
+ mv "#{spec.name}-#{spec.version}.gem", "#{RAILS_ROOT}/pkg"
29
+
30
+ rm "README.textile"
31
+ rm "MIT-LICENSE.txt"
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oauth2_provider
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ThoughtWorks, Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-07-22 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A Rails plugin to OAuth v2.0 enable your rails application
17
+ email: ketan@thoughtworks.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.textile
24
+ - MIT-LICENSE.txt
25
+ files:
26
+ - app/controllers/oauth2/provider/oauth_authorize_controller.rb
27
+ - app/controllers/oauth2/provider/oauth_clients_controller.rb
28
+ - app/controllers/oauth2/provider/oauth_token_controller.rb
29
+ - app/controllers/oauth2/provider/oauth_user_tokens_controller.rb
30
+ - app/models/oauth2/provider/oauth_authorization.rb
31
+ - app/models/oauth2/provider/oauth_client.rb
32
+ - app/models/oauth2/provider/oauth_token.rb
33
+ - app/views/oauth2/provider/layouts/oauth_clients.html.erb
34
+ - app/views/oauth2/provider/oauth_authorize/index.html.erb
35
+ - app/views/oauth2/provider/oauth_clients/edit.html.erb
36
+ - app/views/oauth2/provider/oauth_clients/index.html.erb
37
+ - app/views/oauth2/provider/oauth_clients/new.html.erb
38
+ - app/views/oauth2/provider/oauth_clients/show.html.erb
39
+ - app/views/oauth2/provider/oauth_user_tokens/index.html.erb
40
+ - config/routes.rb
41
+ - generators/oauth2_provider/oauth2_provider_generator.rb
42
+ - generators/oauth2_provider/templates/config/initializers/oauth2_provider.rb
43
+ - generators/oauth2_provider/templates/db/migrate/create_oauth_authorizations.rb
44
+ - generators/oauth2_provider/templates/db/migrate/create_oauth_clients.rb
45
+ - generators/oauth2_provider/templates/db/migrate/create_oauth_tokens.rb
46
+ - init.rb
47
+ - lib/oauth2/provider/application_controller_methods.rb
48
+ - lib/oauth2/provider/clock.rb
49
+ - MIT-LICENSE.txt
50
+ - README.textile
51
+ - tasks/gem.rake
52
+ has_rdoc: true
53
+ homepage: http://github.com/ThoughtWorksStudios/oauth2_provider
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.5
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: A Rails plugin to OAuth v2.0 enable your rails application
80
+ test_files: []
81
+