oauth_provider 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|