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.
- data/CHANGELOG +306 -0
- data/HACKING.textile +45 -0
- data/NOTICE.textile +6 -0
- data/README.textile +11 -3
- data/WHAT_IS_OAUTH.textile +165 -0
- data/app/controllers/oauth_authorize_controller.rb +69 -0
- data/app/controllers/oauth_clients_controller.rb +79 -0
- data/app/controllers/oauth_token_controller.rb +59 -0
- data/app/controllers/oauth_user_tokens_controller.rb +61 -0
- data/app/models/oauth2/provider/oauth_authorization.rb +4 -0
- data/app/models/oauth2/provider/oauth_client.rb +14 -3
- data/app/views/{oauth2/provider/layouts → layouts}/oauth_clients.html.erb +0 -0
- data/app/views/oauth_authorize/index.html.erb +17 -0
- data/app/views/oauth_clients/_form.html.erb +27 -0
- data/app/views/oauth_clients/edit.html.erb +7 -0
- data/app/views/oauth_clients/index.html.erb +53 -0
- data/app/views/oauth_clients/new.html.erb +7 -0
- data/app/views/{oauth2/provider/oauth_clients → oauth_clients}/show.html.erb +0 -0
- data/app/views/oauth_user_tokens/index.html.erb +28 -0
- data/config/routes.rb +15 -9
- data/generators/oauth2_provider/USAGE +12 -0
- data/generators/oauth2_provider/templates/config/initializers/oauth2_provider.rb +3 -0
- data/lib/ext/validatable_ext.rb +27 -0
- data/lib/oauth2/provider/a_r_datasource.rb +13 -1
- data/lib/oauth2/provider/application_controller_methods.rb +32 -20
- data/lib/oauth2/provider/configuration.rb +39 -0
- data/lib/oauth2/provider/in_memory_datasource.rb +8 -0
- data/lib/oauth2/provider/model_base.rb +59 -10
- data/lib/oauth2/provider/ssl_helper.rb +42 -0
- data/lib/oauth2/provider/transaction_helper.rb +24 -0
- data/lib/oauth2/provider/url_parser.rb +17 -0
- data/lib/oauth2_provider.rb +3 -6
- data/oauth2_provider.gemspec +15 -6
- metadata +81 -26
- data/app/controllers/oauth2/provider/oauth_authorize_controller.rb +0 -68
- data/app/controllers/oauth2/provider/oauth_clients_controller.rb +0 -56
- data/app/controllers/oauth2/provider/oauth_token_controller.rb +0 -58
- data/app/controllers/oauth2/provider/oauth_user_tokens_controller.rb +0 -29
- data/app/views/oauth2/provider/oauth_authorize/index.html.erb +0 -8
- data/app/views/oauth2/provider/oauth_clients/edit.html.erb +0 -20
- data/app/views/oauth2/provider/oauth_clients/index.html.erb +0 -28
- data/app/views/oauth2/provider/oauth_clients/new.html.erb +0 -21
- data/app/views/oauth2/provider/oauth_user_tokens/index.html.erb +0 -14
- data/tasks/gem.rake +0 -88
data/config/routes.rb
CHANGED
@@ -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
|
-
|
9
|
+
admin_prefix = "" if admin_prefix.blank?
|
10
|
+
user_prefix = "" if user_prefix.blank?
|
9
11
|
|
10
|
-
|
12
|
+
admin_prefix = admin_prefix.gsub(%r{^/}, '').gsub(%r{/$}, '')
|
13
|
+
user_prefix = user_prefix.gsub(%r{^/}, '').gsub(%r{/$}, '')
|
11
14
|
|
12
|
-
map.
|
13
|
-
|
14
|
-
map.connect "#{
|
15
|
-
|
16
|
-
map.connect "#{user_prefix}/oauth/
|
17
|
-
map.connect "#{user_prefix}/oauth/
|
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.
|
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
|
-
|
20
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
token = OauthToken.find_one(:access_token,
|
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
|
-
|
39
|
-
header_field =~ /Token token="(.*)"/
|
49
|
+
!!oauth_token_from_request_header
|
40
50
|
end
|
41
|
-
|
51
|
+
|
42
52
|
def oauth_allowed?
|
43
|
-
|
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
|
-
|
16
|
-
|
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.
|
24
|
-
|
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] =
|
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
|