oauth2_provider 0.2.0 → 0.3.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 (44) hide show
  1. data/CHANGELOG +306 -0
  2. data/HACKING.textile +45 -0
  3. data/NOTICE.textile +6 -0
  4. data/README.textile +11 -3
  5. data/WHAT_IS_OAUTH.textile +165 -0
  6. data/app/controllers/oauth_authorize_controller.rb +69 -0
  7. data/app/controllers/oauth_clients_controller.rb +79 -0
  8. data/app/controllers/oauth_token_controller.rb +59 -0
  9. data/app/controllers/oauth_user_tokens_controller.rb +61 -0
  10. data/app/models/oauth2/provider/oauth_authorization.rb +4 -0
  11. data/app/models/oauth2/provider/oauth_client.rb +14 -3
  12. data/app/views/{oauth2/provider/layouts → layouts}/oauth_clients.html.erb +0 -0
  13. data/app/views/oauth_authorize/index.html.erb +17 -0
  14. data/app/views/oauth_clients/_form.html.erb +27 -0
  15. data/app/views/oauth_clients/edit.html.erb +7 -0
  16. data/app/views/oauth_clients/index.html.erb +53 -0
  17. data/app/views/oauth_clients/new.html.erb +7 -0
  18. data/app/views/{oauth2/provider/oauth_clients → oauth_clients}/show.html.erb +0 -0
  19. data/app/views/oauth_user_tokens/index.html.erb +28 -0
  20. data/config/routes.rb +15 -9
  21. data/generators/oauth2_provider/USAGE +12 -0
  22. data/generators/oauth2_provider/templates/config/initializers/oauth2_provider.rb +3 -0
  23. data/lib/ext/validatable_ext.rb +27 -0
  24. data/lib/oauth2/provider/a_r_datasource.rb +13 -1
  25. data/lib/oauth2/provider/application_controller_methods.rb +32 -20
  26. data/lib/oauth2/provider/configuration.rb +39 -0
  27. data/lib/oauth2/provider/in_memory_datasource.rb +8 -0
  28. data/lib/oauth2/provider/model_base.rb +59 -10
  29. data/lib/oauth2/provider/ssl_helper.rb +42 -0
  30. data/lib/oauth2/provider/transaction_helper.rb +24 -0
  31. data/lib/oauth2/provider/url_parser.rb +17 -0
  32. data/lib/oauth2_provider.rb +3 -6
  33. data/oauth2_provider.gemspec +15 -6
  34. metadata +81 -26
  35. data/app/controllers/oauth2/provider/oauth_authorize_controller.rb +0 -68
  36. data/app/controllers/oauth2/provider/oauth_clients_controller.rb +0 -56
  37. data/app/controllers/oauth2/provider/oauth_token_controller.rb +0 -58
  38. data/app/controllers/oauth2/provider/oauth_user_tokens_controller.rb +0 -29
  39. data/app/views/oauth2/provider/oauth_authorize/index.html.erb +0 -8
  40. data/app/views/oauth2/provider/oauth_clients/edit.html.erb +0 -20
  41. data/app/views/oauth2/provider/oauth_clients/index.html.erb +0 -28
  42. data/app/views/oauth2/provider/oauth_clients/new.html.erb +0 -21
  43. data/app/views/oauth2/provider/oauth_user_tokens/index.html.erb +0 -14
  44. data/tasks/gem.rake +0 -88
@@ -3,17 +3,23 @@
3
3
 
4
4
  ActionController::Routing::Routes.draw do |map|
5
5
 
6
- admin_prefix=ENV['ADMIN_OAUTH_URL_PREFIX']
6
+ admin_prefix= ENV['ADMIN_OAUTH_URL_PREFIX']
7
+ user_prefix= ENV['USER_OAUTH_URL_PREFIX']
7
8
 
8
- map.resources :oauth_clients, :controller => 'Oauth2::Provider::OauthClients', :as => "#{admin_prefix}oauth/clients"
9
+ admin_prefix = "" if admin_prefix.blank?
10
+ user_prefix = "" if user_prefix.blank?
9
11
 
10
- user_prefix=ENV['USER_OAUTH_URL_PREFIX']
12
+ admin_prefix = admin_prefix.gsub(%r{^/}, '').gsub(%r{/$}, '')
13
+ user_prefix = user_prefix.gsub(%r{^/}, '').gsub(%r{/$}, '')
11
14
 
12
- map.connect "#{user_prefix}/oauth/authorize", :controller => 'Oauth2::Provider::OauthAuthorize', :action => :authorize, :conditions => {:method => :post}
13
- map.connect "#{user_prefix}/oauth/authorize", :controller => 'Oauth2::Provider::OauthAuthorize', :action => :index, :conditions => {:method => :get}
14
- map.connect "#{user_prefix}/oauth/token", :controller => 'Oauth2::Provider::OauthToken', :action => :get_token, :conditions => {:method => :post}
15
-
16
- map.connect "#{user_prefix}/oauth/user_tokens/revoke/:token_id", :controller => 'Oauth2::Provider::OauthUserTokens', :action => :revoke, :conditions => {:method => :delete}
17
- map.connect "#{user_prefix}/oauth/user_tokens", :controller => 'Oauth2::Provider::OauthUserTokens', :action => :index, :conditions => {:method => :get}
15
+ map.resources :oauth_clients, :controller => 'oauth_clients', :as => admin_prefix + (admin_prefix.blank? ? "" : "/") + "oauth/clients"
16
+
17
+ map.connect "#{admin_prefix}/oauth/user_tokens/revoke_by_admin", :controller => 'oauth_user_tokens', :action => :revoke_by_admin, :conditions => {:method => :delete}
18
+
19
+ map.connect "#{user_prefix}/oauth/authorize", :controller => 'oauth_authorize', :action => :authorize, :conditions => {:method => :post}
20
+ map.connect "#{user_prefix}/oauth/authorize", :controller => 'oauth_authorize', :action => :index, :conditions => {:method => :get}
21
+ map.connect "#{user_prefix}/oauth/token", :controller => 'oauth_token', :action => :get_token, :conditions => {:method => :post}
22
+ map.connect "#{user_prefix}/oauth/user_tokens/revoke/:token_id", :controller => 'oauth_user_tokens', :action => :revoke, :conditions => {:method => :delete}
23
+ map.connect "#{user_prefix}/oauth/user_tokens", :controller => 'oauth_user_tokens', :action => :index, :conditions => {:method => :get}
18
24
 
19
25
  end
@@ -0,0 +1,12 @@
1
+ Description:
2
+ Generates configuration and migrations necessary to configure
3
+ and run this plugin!
4
+
5
+ Example:
6
+ ./script/generate oauth2_provider
7
+
8
+ This will create:
9
+ config/initializers/oauth2_provider.rb
10
+ db/migrate/TIMESTAMP_create_oauth_authorizations.rb
11
+ db/migrate/TIMESTAMP_create_oauth_clients.rb
12
+ db/migrate/TIMESTAMP_create_oauth_tokens.rb
@@ -25,5 +25,8 @@ module Oauth2
25
25
  # use host app's custom authorization filter to protect OauthClientsController
26
26
  OauthClientsController.before_filter(:ensure_admin_user)
27
27
 
28
+ # use host app's custom authorization filter to protect admin actions on OauthUserTokensController
29
+ OauthUserTokensController.before_filter(:ensure_admin_user, :only => [:revoke_by_admin])
30
+
28
31
  end
29
32
  end
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2010 ThoughtWorks Inc. (http://thoughtworks.com)
2
+ # Licenced under the MIT License (http://www.opensource.org/licenses/mit-license.php)
3
+
4
+ require 'validatable'
5
+
6
+ module Validatable
7
+ class Errors
8
+ unless method_defined?(:add_without_humanize_options)
9
+ alias :add_without_humanize_options :add
10
+ def add(attribute, message, humanized_name=nil) #:nodoc:
11
+ humanized_names[attribute.to_sym] = humanized_name
12
+ add_without_humanize_options(attribute, message)
13
+ end
14
+
15
+ alias :humanize_without_humanize_options :humanize
16
+
17
+ def humanize(lower_case_and_underscored_word) #:nodoc:
18
+ humanized_names[lower_case_and_underscored_word.to_sym] || humanize_without_humanize_options(lower_case_and_underscored_word)
19
+ end
20
+
21
+ private
22
+ def humanized_names
23
+ @humanized_names ||= {}
24
+ end
25
+ end
26
+ end
27
+ end
@@ -22,6 +22,10 @@ if defined?(ActiveRecord)
22
22
  def reset
23
23
 
24
24
  end
25
+
26
+ def transaction(&block)
27
+ ActiveRecord::Base.transaction(&block)
28
+ end
25
29
 
26
30
  def find_oauth_client_by_id(id)
27
31
  OauthClientDto.find_by_id(id)
@@ -30,6 +34,14 @@ if defined?(ActiveRecord)
30
34
  def find_oauth_client_by_client_id(client_id)
31
35
  OauthClientDto.find_by_client_id(client_id)
32
36
  end
37
+
38
+ def find_oauth_client_by_name(name)
39
+ OauthClientDto.find_by_name(name)
40
+ end
41
+
42
+ def find_oauth_client_by_redirect_uri(redirect_uri)
43
+ OauthClientDto.find_by_redirect_uri(redirect_uri)
44
+ end
33
45
 
34
46
  def find_all_oauth_client
35
47
  OauthClientDto.all
@@ -94,7 +106,7 @@ if defined?(ActiveRecord)
94
106
  private
95
107
 
96
108
  def save(dto_klass, attrs)
97
- dto = dto_klass.find_by_id(attrs[:id])
109
+ dto = dto_klass.find_by_id(attrs[:id]) unless attrs[:id].blank?
98
110
  if dto
99
111
  dto.update_attributes(attrs)
100
112
  else
@@ -3,44 +3,56 @@
3
3
 
4
4
  module Oauth2
5
5
  module Provider
6
+
7
+ class HttpsRequired < StandardError
8
+ end
9
+
6
10
  module ApplicationControllerMethods
7
11
 
8
12
  def self.included(controller_class)
9
- controller_class.cattr_accessor :oauth_options, :oauth_options_proc
10
-
11
- def controller_class.oauth_allowed(options = {}, &block)
12
- raise 'options cannot contain both :only and :except' if options[:only] && options[:except]
13
+ controller_class.extend(ClassMethods)
14
+ end
13
15
 
16
+ module ClassMethods
17
+ def oauth_allowed(options = {}, &block)
18
+ raise 'options cannot contain both :only and :except' if options[:only] && options[:except]
19
+
14
20
  [:only, :except].each do |k|
15
21
  if values = options[k]
16
22
  options[k] = Array(values).map(&:to_s).to_set
17
23
  end
18
- end
19
- self.oauth_options = options
20
- self.oauth_options_proc = block
24
+ end
25
+ write_inheritable_attribute(:oauth_options, options)
26
+ write_inheritable_attribute(:oauth_options_proc, block)
21
27
  end
22
-
23
28
  end
24
29
 
25
30
  protected
26
-
31
+
27
32
  def user_id_for_oauth_access_token
28
33
  return nil unless oauth_allowed?
29
- header_field = request.headers["Authorization"]
30
-
31
- if header_field =~ /Token token="(.*)"/
32
- token = OauthToken.find_one(:access_token, $1)
34
+
35
+ if looks_like_oauth_request?
36
+ raise HttpsRequired.new("HTTPS is required for OAuth Authorizations") unless (request.ssl? || !::Oauth2::Provider::Configuration.require_ssl_for_oauth)
37
+ token = OauthToken.find_one(:access_token, oauth_token_from_request_header)
33
38
  token.user_id if (token && !token.expired?)
34
39
  end
35
40
  end
36
-
41
+
42
+ def oauth_token_from_request_header
43
+ if request.headers["Authorization"] =~ /Token token="(.*)"/
44
+ return $1
45
+ end
46
+ end
47
+
37
48
  def looks_like_oauth_request?
38
- header_field = request.headers["Authorization"]
39
- header_field =~ /Token token="(.*)"/
49
+ !!oauth_token_from_request_header
40
50
  end
41
-
51
+
42
52
  def oauth_allowed?
43
- if (oauth_options_proc && !oauth_options_proc.call(self))
53
+ oauth_options_proc = self.class.read_inheritable_attribute(:oauth_options_proc)
54
+ oauth_options = self.class.read_inheritable_attribute(:oauth_options)
55
+ if oauth_options_proc && !oauth_options_proc.call(self)
44
56
  false
45
57
  else
46
58
  return false if oauth_options.nil?
@@ -49,7 +61,7 @@ module Oauth2
49
61
  (oauth_options[:except] && !oauth_options[:except].include?(action_name))
50
62
  end
51
63
  end
52
-
64
+
53
65
  end
54
66
  end
55
- end
67
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2010 ThoughtWorks Inc. (http://thoughtworks.com)
2
+ # Licenced under the MIT License (http://www.opensource.org/licenses/mit-license.php)
3
+
4
+ module Oauth2
5
+ module Provider
6
+ module Configuration
7
+ def self.def_properties(*names)
8
+ names.each do |name|
9
+ class_eval(<<-EOS, __FILE__, __LINE__)
10
+ @@__#{name} = nil
11
+ def #{name}
12
+ @@__#{name}.respond_to?(:call) ? @@__#{name}.call : @@__#{name}
13
+ end
14
+
15
+ def #{name}=(value_or_proc)
16
+ @@__#{name} = value_or_proc
17
+ end
18
+ module_function :#{name}, :#{name}=
19
+ EOS
20
+
21
+ self.send(:module_function, name, "#{name}=")
22
+ end
23
+ end
24
+
25
+ def_properties :ssl_base_url, :require_ssl_for_oauth
26
+
27
+ def self.ssl_base_url_as_url_options
28
+ result = {:only_path => false}
29
+ return result if ssl_base_url.blank?
30
+ uri = URIParser.parse(ssl_base_url)
31
+ raise "SSL base URL must be https" unless uri.scheme == 'https'
32
+ result.merge!(:protocol => uri.scheme, :host => uri.host, :port => uri.port)
33
+ result.delete(:port) if (uri.port == uri.default_port || uri.port == -1)
34
+ result
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -36,6 +36,14 @@ module Oauth2
36
36
  def find_oauth_client_by_client_id(client_id)
37
37
  @@oauth_clients.find{|i| i.client_id.to_s == client_id.to_s}
38
38
  end
39
+
40
+ def find_oauth_client_by_name(name)
41
+ @@oauth_clients.find{|i| i.name == name}
42
+ end
43
+
44
+ def find_oauth_client_by_redirect_uri(redirect_uri)
45
+ @@oauth_clients.find{|i| i.redirect_uri == redirect_uri}
46
+ end
39
47
 
40
48
  def find_all_oauth_client
41
49
  @@oauth_clients
@@ -1,5 +1,6 @@
1
1
  # Copyright (c) 2010 ThoughtWorks Inc. (http://thoughtworks.com)
2
2
  # Licenced under the MIT License (http://www.opensource.org/licenses/mit-license.php)
3
+ require 'validatable'
3
4
 
4
5
  module Oauth2
5
6
  module Provider
@@ -12,24 +13,27 @@ module Oauth2
12
13
  class ModelBase
13
14
  include Validatable
14
15
  CONVERTORS = {
15
- :integer => Proc.new { |v| v.to_i },
16
- :string => Proc.new { |v| v.to_s }
16
+ :integer => Proc.new { |v| v ? v.to_i : nil },
17
+ :string => Proc.new { |v| v.to_s }
17
18
  }.with_indifferent_access
18
19
 
19
20
  class_inheritable_hash :db_columns
20
21
  self.db_columns = {}
21
22
 
22
23
  def self.columns(*names)
23
- names.each do |name|
24
- column_name, convertor = (Hash === name) ?
25
- [name.keys.first, CONVERTORS[name.values.first]] :
26
- [name, CONVERTORS[:string]]
24
+ options = names.extract_options!
25
+ names.each do |column_name|
27
26
  attr_accessor column_name
28
- self.db_columns[column_name.to_s] = convertor
27
+ self.db_columns[column_name.to_s] = CONVERTORS[:string]
28
+ end
29
+
30
+ options.each do |column_name, converter|
31
+ attr_accessor column_name
32
+ self.db_columns[column_name.to_s] = CONVERTORS[converter]
29
33
  end
30
34
  end
31
35
 
32
- columns :id
36
+ columns :id => :integer
33
37
 
34
38
  def initialize(attributes={})
35
39
  assign_attributes(attributes)
@@ -49,7 +53,23 @@ module Oauth2
49
53
  ds
50
54
  end
51
55
  end
52
-
56
+
57
+ def self.validates_uniqueness_of(*columns)
58
+ options = columns.extract_options!
59
+ columns.each do |column_name|
60
+ self.validates_each column_name, :logic => lambda {
61
+ if scope = options[:scope]
62
+ dtos = self.class.find_all_with(column_name, self.send(column_name))
63
+ dtos = dtos.select{ |o| o.send(scope) == self.send(scope) }
64
+ errors.add(column_name, 'is a duplicate.', options[:humanized_name]) if dtos.size == 1 && dtos.first.id != self.id
65
+ else
66
+ dto = datasource.send("find_#{self.class.compact_name}_by_#{column_name}", read_attribute(column_name))
67
+ errors.add(column_name, 'is a duplicate.', options[:humanized_name]) if dto && dto.id != self.id
68
+ end
69
+ }
70
+ end
71
+ end
72
+
53
73
  def self.datasource
54
74
  @@datasource ||= default_datasource
55
75
  end
@@ -70,6 +90,22 @@ module Oauth2
70
90
  InMemoryDatasource.new
71
91
  end
72
92
  end
93
+
94
+ def self.transaction(&block)
95
+ if datasource.respond_to?(:transaction)
96
+ result = nil
97
+ datasource.transaction do
98
+ result = yield
99
+ end
100
+ result
101
+ else
102
+ yield
103
+ end
104
+ end
105
+
106
+ def transaction(&block)
107
+ self.class.transaction(&block)
108
+ end
73
109
 
74
110
  def self.find(id)
75
111
  find_by_id(id) || raise(NotFoundException.new("Record not found!"))
@@ -132,6 +168,7 @@ module Oauth2
132
168
 
133
169
  def save
134
170
  before_create if new_record?
171
+ before_save
135
172
  attrs = db_columns.keys.inject({}) do |result, column_name|
136
173
  result[column_name] = read_attribute(column_name)
137
174
  result
@@ -151,13 +188,17 @@ module Oauth2
151
188
 
152
189
  def destroy
153
190
  before_destroy
154
- datasource.send("delete_#{self.class.compact_name}", convert(:id, id) )
191
+ datasource.send("delete_#{self.class.compact_name}", convert(:id, id))
155
192
  end
156
193
 
157
194
  def before_create
158
195
  # for subclasses to override to support hooks.
159
196
  end
160
197
 
198
+ def before_save
199
+ # for subclasses to override to support hooks.
200
+ end
201
+
161
202
  def before_destroy
162
203
  # for subclasses to override to support hooks.
163
204
  end
@@ -181,6 +222,14 @@ module Oauth2
181
222
  attrs.each { |k, v| write_attribute(k, v) }
182
223
  end
183
224
 
225
+ def to_xml(options = {})
226
+ acc = self.db_columns.keys.sort.inject(ActiveSupport::OrderedHash.new) do |acc, key|
227
+ acc[key] = self.send(key)
228
+ acc
229
+ end
230
+ acc.to_xml({:root => self.class.name.demodulize.underscore.downcase}.merge(options))
231
+ end
232
+
184
233
  private
185
234
 
186
235
  def self.convert(column_name, value)
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2010 ThoughtWorks Inc. (http://thoughtworks.com)
2
+ # Licenced under the MIT License (http://www.opensource.org/licenses/mit-license.php)
3
+
4
+ module Oauth2
5
+ module Provider
6
+ module SslHelper
7
+
8
+ def self.included(controller_class)
9
+ controller_class.before_filter :mandatory_ssl
10
+ end
11
+
12
+ protected
13
+ def mandatory_ssl
14
+ return true if !Oauth2::Provider::Configuration.require_ssl_for_oauth
15
+ return true if request.ssl?
16
+
17
+ if ssl_enabled?
18
+ redirect_to params.merge(ssl_base_url_as_url_options)
19
+ else
20
+ error = 'This page can only be accessed using HTTPS.'
21
+ flash.now[:error] = error
22
+ render(:text => '', :layout => true, :status => :forbidden)
23
+ end
24
+ false
25
+ end
26
+
27
+ private
28
+
29
+ def ssl_base_url_as_url_options
30
+ Oauth2::Provider::Configuration.ssl_base_url_as_url_options
31
+ end
32
+
33
+ def ssl_base_url
34
+ Oauth2::Provider::Configuration.ssl_base_url
35
+ end
36
+
37
+ def ssl_enabled?
38
+ !ssl_base_url.blank? && ssl_base_url_as_url_options[:protocol] == 'https'
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2010 ThoughtWorks Inc. (http://thoughtworks.com)
2
+ # Licenced under the MIT License (http://www.opensource.org/licenses/mit-license.php)
3
+
4
+ module Oauth2
5
+ module Provider
6
+ module TransactionHelper
7
+ def self.included(receiver)
8
+ receiver.extend ClassMethods
9
+ end
10
+
11
+ class TransactionFilter
12
+ def filter(controller, &block)
13
+ ModelBase.transaction(&block)
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def transaction_actions(*actions)
19
+ self.around_filter TransactionFilter.new, :only => actions
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end