oauth_provider 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +151 -0
- data/lib/oauth_provider.rb +52 -0
- data/lib/oauth_provider/backends.rb +9 -0
- data/lib/oauth_provider/backends/abstract.rb +75 -0
- data/lib/oauth_provider/backends/data_mapper.rb +115 -0
- data/lib/oauth_provider/backends/data_mapper/consumer.rb +25 -0
- data/lib/oauth_provider/backends/data_mapper/user_access.rb +25 -0
- data/lib/oauth_provider/backends/data_mapper/user_request.rb +25 -0
- data/lib/oauth_provider/backends/in_memory.rb +47 -0
- data/lib/oauth_provider/backends/mysql.rb +90 -0
- data/lib/oauth_provider/backends/sequel.rb +81 -0
- data/lib/oauth_provider/backends/sqlite3.rb +75 -0
- data/lib/oauth_provider/consumer.rb +40 -0
- data/lib/oauth_provider/fixes.rb +8 -0
- data/lib/oauth_provider/provider.rb +73 -0
- data/lib/oauth_provider/token.rb +25 -0
- data/lib/oauth_provider/user_access.rb +25 -0
- data/lib/oauth_provider/user_request.rb +46 -0
- data/lib/oauth_provider/version.rb +3 -0
- data/spec/backend_spec.rb +3 -0
- data/spec/backends/abstract_spec.rb +11 -0
- data/spec/consumer_spec.rb +65 -0
- data/spec/helpers/backend_helper.rb +85 -0
- data/spec/provider_spec.rb +107 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/token_spec.rb +20 -0
- data/spec/user_access_spec.rb +10 -0
- data/spec/user_request_spec.rb +52 -0
- metadata +116 -0
data/README
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--------------------------------------------------------------------------------
|
2
|
+
- OAuth Provider library in Ruby
|
3
|
+
--------------------------------------------------------------------------------
|
4
|
+
|
5
|
+
--------------------------------------------------------------------------------
|
6
|
+
- 1) Getting the library setup
|
7
|
+
- 2) Creating a provider
|
8
|
+
- 3) Adding a consumer
|
9
|
+
- 4) Issuing a request token
|
10
|
+
- 5) Authorizing a request token
|
11
|
+
- 6) Upgrading a request token to an access token
|
12
|
+
- 7) Confirming access for an access token
|
13
|
+
--------------------------------------------------------------------------------
|
14
|
+
|
15
|
+
--------------------------------------------------------------------------------
|
16
|
+
- 1) Getting the library setup
|
17
|
+
--------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
You can currently only download the source and build a gem.
|
20
|
+
It will be put on rubyforge once it is more feature-some.
|
21
|
+
|
22
|
+
# git clone git://github.com/halorgium/oauth_provider.git
|
23
|
+
# rake package
|
24
|
+
|
25
|
+
--------------------------------------------------------------------------------
|
26
|
+
- 2) Getting the library setup
|
27
|
+
--------------------------------------------------------------------------------
|
28
|
+
|
29
|
+
Create a provider to allow you to interact issue request tokens etc.
|
30
|
+
There are several backends to allow you to use this for real and in testing.
|
31
|
+
|
32
|
+
The in-memory backend is best for testing, it allows you to not have the
|
33
|
+
overhead of a database.
|
34
|
+
|
35
|
+
# provider = OAuthProvider.create(:in_memory)
|
36
|
+
|
37
|
+
The DataMapper backend is currently the only real backend, you can provide a
|
38
|
+
repository which will allow you to use a different database connection.
|
39
|
+
|
40
|
+
# provider = OAuthProvider.create(:data_mapper, :some_oauth_repository)
|
41
|
+
|
42
|
+
--------------------------------------------------------------------------------
|
43
|
+
- 3) Adding a consumer
|
44
|
+
--------------------------------------------------------------------------------
|
45
|
+
|
46
|
+
To add a consumer to the provider, you need to provide a callback URL.
|
47
|
+
|
48
|
+
# consumer = provider.add_consumer("http://myconsumer.com/token")
|
49
|
+
|
50
|
+
You should store the consumer shared key in your database so you can associate
|
51
|
+
your users with the tokens they own.
|
52
|
+
|
53
|
+
# Consumer.create("My Consumer", consumer.shared_key)
|
54
|
+
|
55
|
+
--------------------------------------------------------------------------------
|
56
|
+
- 4) Issuing a request token
|
57
|
+
--------------------------------------------------------------------------------
|
58
|
+
|
59
|
+
Now you can issue a request token, this will save the token for later access.
|
60
|
+
You need to pass in the raw request object which your web framework uses and
|
61
|
+
require the correct request-proxy.
|
62
|
+
|
63
|
+
Rails (ActionController):
|
64
|
+
# require 'oauth/request_proxy/action_controller_request'
|
65
|
+
XMPP4R:
|
66
|
+
# require 'oauth/request_proxy/jabber_request'
|
67
|
+
Net::HTTP:
|
68
|
+
# require 'oauth/request_proxy/net_http'
|
69
|
+
Sinatra/Merb (Rack):
|
70
|
+
# require 'oauth/request_proxy/rack_request'
|
71
|
+
|
72
|
+
Once that file is required, you can ask the provider to issue a token.
|
73
|
+
|
74
|
+
# user_request = provider.issue_request(request)
|
75
|
+
|
76
|
+
You should save this token in your database to connect this token with a
|
77
|
+
particular user.
|
78
|
+
|
79
|
+
# current_user.tokens.create(:consumer_shared_key => user_request.consumer.shared_key,
|
80
|
+
# :shared_key => user_request.shared_key)
|
81
|
+
|
82
|
+
This object allows you to access the query_string which should be returned
|
83
|
+
to the consumer.
|
84
|
+
This is the form: oauth_token=ABCDE&oauth_token_secret=SECRET123
|
85
|
+
|
86
|
+
# user_request.query_string
|
87
|
+
|
88
|
+
Now it is up to the consumer to redirect the user to your authorization
|
89
|
+
screen. To locate the token which corresponds with the shared key (usually
|
90
|
+
the 'oauth_token' parameter in the request) you need to
|
91
|
+
|
92
|
+
--------------------------------------------------------------------------------
|
93
|
+
- 5) Authorizing a request token
|
94
|
+
--------------------------------------------------------------------------------
|
95
|
+
|
96
|
+
Once you have determined that the user wishes to authorize the request. You
|
97
|
+
should display the consumer information to the user.
|
98
|
+
|
99
|
+
An example ERB view might be:
|
100
|
+
|
101
|
+
# <p>You are about to authorize <%= token.consumer.name %> to access your account %></p>
|
102
|
+
# <p>Do you want this to happen?</p>
|
103
|
+
# <p><a href="/authorize?oauth_token=<%= token.shared_key %>Authorize it</a>
|
104
|
+
|
105
|
+
At this point, you can also store any access control information to allow this
|
106
|
+
consumer to perhaps only have read-access to the user's information.
|
107
|
+
|
108
|
+
Then in the 'authorize' action you would tell the provider to authorize this
|
109
|
+
request token and redirect back to the consumer callback URL.
|
110
|
+
|
111
|
+
# user_request.authorize
|
112
|
+
# redirect_to user_request.callback
|
113
|
+
|
114
|
+
--------------------------------------------------------------------------------
|
115
|
+
- 6) Upgrading a request token to an access token
|
116
|
+
--------------------------------------------------------------------------------
|
117
|
+
|
118
|
+
Now that the request token is authorized by the user, the consumer can upgrade
|
119
|
+
this token to an access token.
|
120
|
+
|
121
|
+
# user_access = provider.upgrade_request(request)
|
122
|
+
|
123
|
+
If the request token is not yet authorized, an exception will be raised. The
|
124
|
+
exception class is 'OAuthProvider::UserRequestNotAuthorized'.
|
125
|
+
|
126
|
+
If the request token is authorized, the request token will be destroyed and
|
127
|
+
a access token will be generated and returned.
|
128
|
+
|
129
|
+
Now you can save this into your database.
|
130
|
+
|
131
|
+
# token = current_user.tokens.find_by_shared_key(user_access.request_shared_key)
|
132
|
+
# token.update_attributes(:access => true, :shared_key => user_access.shared_key)
|
133
|
+
|
134
|
+
And return the query string back to the consumer
|
135
|
+
|
136
|
+
# user_access.query_string
|
137
|
+
|
138
|
+
--------------------------------------------------------------------------------
|
139
|
+
- 7) Confirming access for an access token
|
140
|
+
--------------------------------------------------------------------------------
|
141
|
+
|
142
|
+
At this point, the consumer should have a valid access token and can make API
|
143
|
+
requests. You can ask the provider to confirm that the access token is valid.
|
144
|
+
|
145
|
+
# user_access = provider.confirm_access(request)
|
146
|
+
|
147
|
+
Now you can find the user token which corresponds to the shared_key.
|
148
|
+
|
149
|
+
# token = current_user.tokens.first(:access => true, :shared_key => user_access.shared_key)
|
150
|
+
|
151
|
+
You are now ready to respond to the API request as needed!
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
require 'oauth/server'
|
3
|
+
require 'oauth/signature'
|
4
|
+
|
5
|
+
module OAuthProvider
|
6
|
+
class Error < StandardError; end
|
7
|
+
class NotImplemented < Error; end
|
8
|
+
class ConsumerNotFound < Error
|
9
|
+
def initialize(shared_key)
|
10
|
+
super("No Consumer with shared key: #{shared_key.inspect}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
class UserRequestNotFound < Error
|
14
|
+
def initialize(shared_key)
|
15
|
+
super("No User Request with shared key: #{shared_key.inspect}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
class UserAccessNotFound < Error
|
19
|
+
def initialize(shared_key)
|
20
|
+
super("No User Access with shared key: #{shared_key.inspect}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
class UserRequestNotAuthorized < Error
|
24
|
+
def initialize(user_request)
|
25
|
+
super("The User Request is not yet authorized by the User: #{user_request.shared_key.inspect}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
class VerficationFailed < Error; end
|
29
|
+
class IncompleteToken < Error; end
|
30
|
+
|
31
|
+
class DuplicateCallback < Error
|
32
|
+
def initialize(consumer)
|
33
|
+
super("The callback #{consumer.callback.inspect} is already used by another consumer")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.create(backend_type, *args)
|
38
|
+
Backends.for(backend_type, *args).provider
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
$:.unshift File.dirname(__FILE__)
|
43
|
+
|
44
|
+
require 'oauth_provider/fixes'
|
45
|
+
require 'oauth_provider/token'
|
46
|
+
require 'oauth_provider/provider'
|
47
|
+
require 'oauth_provider/consumer'
|
48
|
+
require 'oauth_provider/user_request'
|
49
|
+
require 'oauth_provider/user_access'
|
50
|
+
|
51
|
+
require 'oauth_provider/backends'
|
52
|
+
require 'oauth_provider/backends/abstract'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module OAuthProvider
|
2
|
+
module Backends
|
3
|
+
class Abstract
|
4
|
+
def provider
|
5
|
+
@provider ||= Provider.new(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_consumer(provider, callback, token)
|
9
|
+
consumer = Consumer.new(self, provider, callback, token)
|
10
|
+
create_consumer(consumer)
|
11
|
+
consumer
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_consumer(consumer)
|
15
|
+
raise NotImplemented, "Implement #create_consumer in #{self.class}"
|
16
|
+
end
|
17
|
+
protected :create_consumer
|
18
|
+
|
19
|
+
def find_consumer(shared_key)
|
20
|
+
raise NotImplemented, "Implement #find_consumer in #{self.class}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def save_consumer(shared_key)
|
24
|
+
raise NotImplemented, "Implement #save_consumer in #{self.class}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy_consumer(shared_key)
|
28
|
+
raise NotImplemented, "Implement #destroy_consumer in #{self.class}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_user_request(consumer, authorized, token)
|
32
|
+
user_request = UserRequest.new(self, consumer, authorized, token)
|
33
|
+
create_user_request(user_request)
|
34
|
+
user_request
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_user_request(user_request)
|
38
|
+
raise NotImplemented, "Implement #create_user_request in #{self.class}"
|
39
|
+
end
|
40
|
+
protected :create_user_request
|
41
|
+
|
42
|
+
def find_user_request(shared_key)
|
43
|
+
raise NotImplemented, "Implement #find_user_request in #{self.class}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_user_request(user_request)
|
47
|
+
raise NotImplemented, "Implement #save_user_request in #{self.class}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def destroy_user_request(user_request)
|
51
|
+
raise NotImplemented, "Implement #destroy_user_request in #{self.class}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_user_access(user_request, token)
|
55
|
+
user_access = UserAccess.new(self, user_request.consumer, user_request.shared_key, token)
|
56
|
+
create_user_access(user_access)
|
57
|
+
destroy_user_request(user_request)
|
58
|
+
user_access
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_user_access(user_access)
|
62
|
+
raise NotImplemented, "Implement #create_user_access in #{self.class}"
|
63
|
+
end
|
64
|
+
protected :create_user_access
|
65
|
+
|
66
|
+
def find_user_access(shared_key)
|
67
|
+
raise NotImplemented, "Implement #find_user_access in #{self.class}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def destroy_user_access(user_access)
|
71
|
+
raise NotImplemented, "Implement #destroy_user_access in #{self.class}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-validations'
|
3
|
+
|
4
|
+
module OAuthProvider
|
5
|
+
module Backends
|
6
|
+
class DataMapper < Abstract
|
7
|
+
def initialize(repository = :default)
|
8
|
+
@repository = repository
|
9
|
+
end
|
10
|
+
|
11
|
+
def consumers
|
12
|
+
with_repository do
|
13
|
+
Consumer.all.map do |c|
|
14
|
+
c.to_oauth(self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_consumer(consumer)
|
20
|
+
with_repository do
|
21
|
+
raise DuplicateCallback.new(consumer) if Consumer.first(:callback => consumer.callback)
|
22
|
+
model = Consumer.new(:callback => consumer.callback,
|
23
|
+
:shared_key => consumer.shared_key,
|
24
|
+
:secret_key => consumer.secret_key)
|
25
|
+
model.save || raise("Failed to create Consumer: #{model.inspect}, #{model.errors.inspect}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_consumer(shared_key)
|
30
|
+
consumer = consumer_for(shared_key)
|
31
|
+
consumer && consumer.to_oauth(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def destroy_consumer(consumer)
|
35
|
+
consumer = consumer_for(consumer.shared_key)
|
36
|
+
consumer && consumer.destroy
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_user_request(user_request)
|
40
|
+
with_repository do
|
41
|
+
if consumer = consumer_for(user_request.consumer.shared_key)
|
42
|
+
consumer.user_requests.create(:shared_key => user_request.shared_key,
|
43
|
+
:secret_key => user_request.secret_key,
|
44
|
+
:authorized => user_request.authorized?)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_user_request(shared_key)
|
50
|
+
user_request = user_request_for(shared_key)
|
51
|
+
user_request && user_request.to_oauth(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def save_user_request(user_request)
|
55
|
+
if model = user_request_for(user_request.shared_key)
|
56
|
+
model.authorized = user_request.authorized?
|
57
|
+
model.save || raise("Failed to save UserRequest: #{user_request.shared_key}, #{model.errors.inspect}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy_user_request(user_request)
|
62
|
+
user_request = user_request_for(user_request.shared_key)
|
63
|
+
user_request && user_request.destroy
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_user_access(user_access)
|
67
|
+
with_repository do
|
68
|
+
if consumer = consumer_for(user_access.consumer.shared_key)
|
69
|
+
u = consumer.user_accesses.create(:request_shared_key => user_access.request_shared_key,
|
70
|
+
:shared_key => user_access.shared_key,
|
71
|
+
:secret_key => user_access.secret_key)
|
72
|
+
else
|
73
|
+
raise ConsumerNotFound.new(user_access.consumer.shared_key)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_user_access(shared_key)
|
79
|
+
with_repository do
|
80
|
+
user_access = UserAccess.first(:shared_key => shared_key)
|
81
|
+
user_access && user_access.to_oauth(self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def with_repository(&block)
|
87
|
+
::DataMapper.repository(@repository) do
|
88
|
+
yield
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def consumer_for(shared_key)
|
93
|
+
with_repository do
|
94
|
+
Consumer.first(:shared_key => shared_key)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def user_request_for(shared_key)
|
99
|
+
with_repository do
|
100
|
+
UserRequest.first(:shared_key => shared_key)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def user_access_for(shared_key)
|
105
|
+
with_repository do
|
106
|
+
UserAccess.first(:shared_key => shared_key)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
require 'oauth_provider/backends/data_mapper/consumer'
|
114
|
+
require 'oauth_provider/backends/data_mapper/user_request'
|
115
|
+
require 'oauth_provider/backends/data_mapper/user_access'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module OAuthProvider
|
2
|
+
module Backends
|
3
|
+
class DataMapper
|
4
|
+
class Consumer
|
5
|
+
include ::DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Serial
|
8
|
+
property :callback, String, :unique => true, :nullable => false
|
9
|
+
property :shared_key, String, :unique => true, :nullable => false
|
10
|
+
property :secret_key, String, :unique => true, :nullable => false
|
11
|
+
|
12
|
+
has n, :user_requests, :class_name => '::OAuthProvider::Backends::DataMapper::UserRequest'
|
13
|
+
has n, :user_accesses, :class_name => '::OAuthProvider::Backends::DataMapper::UserAccess'
|
14
|
+
|
15
|
+
def token
|
16
|
+
OAuthProvider::Token.new(shared_key, secret_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_oauth(backend)
|
20
|
+
OAuthProvider::Consumer.new(backend, backend.provider, callback, token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module OAuthProvider
|
2
|
+
module Backends
|
3
|
+
class DataMapper
|
4
|
+
class UserAccess
|
5
|
+
include ::DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Serial
|
8
|
+
property :consumer_id, Integer, :nullable => false
|
9
|
+
property :request_shared_key, String, :nullable => false
|
10
|
+
property :shared_key, String, :unique => true, :nullable => false
|
11
|
+
property :secret_key, String, :unique => true, :nullable => false
|
12
|
+
|
13
|
+
belongs_to :consumer , :class_name => '::OAuthProvider::Backends::DataMapper::Consumer'
|
14
|
+
|
15
|
+
def token
|
16
|
+
OAuthProvider::Token.new(shared_key, secret_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_oauth(backend)
|
20
|
+
OAuthProvider::UserAccess.new(backend, consumer.to_oauth(backend), request_shared_key, token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|