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
@@ -0,0 +1,25 @@
|
|
1
|
+
module OAuthProvider
|
2
|
+
class Token
|
3
|
+
def self.generate
|
4
|
+
new(generate_key(16), generate_key)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.generate_key(size = 32)
|
8
|
+
Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/,'')
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(shared_key, secret_key)
|
12
|
+
@shared_key, @secret_key = shared_key, secret_key
|
13
|
+
end
|
14
|
+
attr_reader :shared_key, :secret_key
|
15
|
+
|
16
|
+
def query_string
|
17
|
+
OAuth::Token.new(shared_key, secret_key).to_query
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(token)
|
21
|
+
return false unless token.is_a?(Token)
|
22
|
+
[shared_key, secret_key].eql?([token.shared_key, token.secret_key])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module OAuthProvider
|
2
|
+
class UserAccess
|
3
|
+
def initialize(backend, consumer, request_shared_key, token)
|
4
|
+
@backend, @consumer, @request_shared_key, @token = backend, consumer, request_shared_key, token
|
5
|
+
end
|
6
|
+
attr_reader :consumer, :request_shared_key, :token
|
7
|
+
|
8
|
+
def query_string
|
9
|
+
@token.query_string
|
10
|
+
end
|
11
|
+
|
12
|
+
def shared_key
|
13
|
+
@token.shared_key
|
14
|
+
end
|
15
|
+
|
16
|
+
def secret_key
|
17
|
+
@token.secret_key
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(user_access)
|
21
|
+
return false unless user_access.is_a?(UserAccess)
|
22
|
+
[consumer, request_shared_key, token] == [user_access.consumer, user_access.request_shared_key, user_access.token]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module OAuthProvider
|
2
|
+
class UserRequest
|
3
|
+
def initialize(backend, consumer, authorized, token)
|
4
|
+
@backend, @consumer, @authorized, @token = backend, consumer, authorized, token
|
5
|
+
end
|
6
|
+
attr_reader :consumer, :token
|
7
|
+
|
8
|
+
def authorized?
|
9
|
+
@authorized
|
10
|
+
end
|
11
|
+
|
12
|
+
def authorize
|
13
|
+
@authorized = true
|
14
|
+
@backend.save_user_request(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def upgrade(token = nil)
|
18
|
+
if authorized?
|
19
|
+
@backend.add_user_access(self, token || Token.generate)
|
20
|
+
else
|
21
|
+
raise UserRequestNotAuthorized.new(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def callback
|
26
|
+
@consumer.callback
|
27
|
+
end
|
28
|
+
|
29
|
+
def query_string
|
30
|
+
@token.query_string
|
31
|
+
end
|
32
|
+
|
33
|
+
def shared_key
|
34
|
+
@token.shared_key
|
35
|
+
end
|
36
|
+
|
37
|
+
def secret_key
|
38
|
+
@token.secret_key
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(user_request)
|
42
|
+
return false unless user_request.is_a?(UserRequest)
|
43
|
+
[consumer, authorized?, token] == [user_request.consumer, user_request.authorized?, user_request.token]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
describe "The Abstract backend" do
|
2
|
+
%w( create_consumer find_consumer save_consumer destroy_consumer
|
3
|
+
create_user_request find_user_request save_user_request destroy_user_request
|
4
|
+
create_user_access find_user_access destroy_user_access ).each do |method_name|
|
5
|
+
it "does not implement the ##{method_name} method" do
|
6
|
+
backend = OAuthProvider::Backends::Abstract.new
|
7
|
+
lambda { backend.send(method_name, :arg) }.
|
8
|
+
should raise_error(OAuthProvider::NotImplemented, /#{method_name}/)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
describe "A Consumer" do
|
2
|
+
describe "issuing a user request" do
|
3
|
+
it "saves the request" do
|
4
|
+
provider = create_provider
|
5
|
+
consumer = provider.add_consumer("http://foo.com")
|
6
|
+
user_request = consumer.issue_request
|
7
|
+
consumer.find_user_request(user_request.shared_key).should == user_request
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "specifying that it is already authorized" do
|
11
|
+
it "is authorized" do
|
12
|
+
provider = create_provider
|
13
|
+
consumer = provider.add_consumer("http://foo.com")
|
14
|
+
user_request = consumer.issue_request(true)
|
15
|
+
consumer.find_user_request(user_request.shared_key).should be_authorized
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "specifying that it is not authorized" do
|
20
|
+
it "is not authorized" do
|
21
|
+
provider = create_provider
|
22
|
+
consumer = provider.add_consumer("http://foo.com")
|
23
|
+
user_request = consumer.issue_request(false)
|
24
|
+
consumer.find_user_request(user_request.shared_key).should_not be_authorized
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "with a custom token" do
|
29
|
+
it "uses the provided token" do
|
30
|
+
provider = create_provider
|
31
|
+
consumer = provider.add_consumer("http://foo.com")
|
32
|
+
user_request = consumer.issue_request(false, OAuthProvider::Token.new("shared key", "secret key"))
|
33
|
+
consumer.find_user_request("shared key").should == user_request
|
34
|
+
user_request.secret_key.should == "secret key"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can destroy a user request" do
|
40
|
+
provider = create_provider
|
41
|
+
consumer = provider.add_consumer("http://foo.com")
|
42
|
+
user_request = consumer.issue_request
|
43
|
+
consumer.find_user_request(user_request.shared_key).should == user_request
|
44
|
+
consumer.destroy_user_request(user_request).should be_true
|
45
|
+
lambda {consumer.find_user_request(user_request)}.should raise_error(OAuthProvider::UserRequestNotFound)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "finds the same user access for a shared key" do
|
49
|
+
provider = create_provider
|
50
|
+
consumer = provider.add_consumer("http://foo.com")
|
51
|
+
user_request = consumer.issue_request
|
52
|
+
user_request.authorize
|
53
|
+
user_access = user_request.upgrade
|
54
|
+
consumer.find_user_access(user_access.shared_key).should == user_access
|
55
|
+
end
|
56
|
+
|
57
|
+
it "is equal to another consumer when both the callback and token match" do
|
58
|
+
provider = create_provider
|
59
|
+
token1 = OAuthProvider::Token.new("123", "456")
|
60
|
+
token2 = OAuthProvider::Token.new("123", "456")
|
61
|
+
consumer1 = OAuthProvider::Consumer.new(nil, provider, "callback", token1)
|
62
|
+
consumer2 = OAuthProvider::Consumer.new(nil, provider, "callback", token2)
|
63
|
+
consumer1.should == consumer2
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module OAuthBackendHelper
|
4
|
+
module InMemory
|
5
|
+
def self.create
|
6
|
+
OAuthProvider.create(:in_memory)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.setup; end
|
10
|
+
def self.reset; end
|
11
|
+
end
|
12
|
+
|
13
|
+
module DataMapper
|
14
|
+
def self.create
|
15
|
+
OAuthProvider.create(:data_mapper)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.setup
|
19
|
+
require 'dm-core'
|
20
|
+
::DataMapper.setup(:default, "sqlite3:///tmp/oauth_provider_test.sqlite3")
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.reset
|
24
|
+
create
|
25
|
+
::DataMapper.auto_migrate!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Sqlite3
|
30
|
+
PATH = "/tmp/oauth_provider_sqlite3_test.sqlite3" unless defined?(PATH)
|
31
|
+
|
32
|
+
def self.create
|
33
|
+
OAuthProvider.create(:sqlite3, PATH)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.setup; end
|
37
|
+
|
38
|
+
def self.reset
|
39
|
+
FileUtils.rm(PATH) rescue nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Mysql
|
44
|
+
def self.create
|
45
|
+
host = ENV['MYSQL_HOST'] || "localhost"
|
46
|
+
user = ENV['MYSQL_USER'] || "root"
|
47
|
+
password = ENV['MYSQL_PASSWORD'] || ""
|
48
|
+
db = ENV['MYSQL_DB'] || "oauth_provider_test"
|
49
|
+
port = ENV['MYSQL_PORT'] || 3306
|
50
|
+
OAuthProvider.create(:mysql, host, user, password, db, port)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.setup; end
|
54
|
+
|
55
|
+
def self.reset
|
56
|
+
self.create.backend.clear!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def self.setup
|
62
|
+
backend_module.setup
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.reset
|
66
|
+
backend_module.reset
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.provider
|
70
|
+
backend_module.create
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.backend_module
|
74
|
+
klass_name = backend_name.to_s.split('_').map {|e| e.capitalize}.join
|
75
|
+
unless const_defined?(klass_name)
|
76
|
+
$stderr.puts "There is no backend for #{backend_name.inspect}"
|
77
|
+
exit!
|
78
|
+
end
|
79
|
+
const_get(klass_name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.backend_name
|
83
|
+
(ENV["BACKEND"] || "in_memory").to_sym
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A Provider" do
|
4
|
+
describe "adding a consumer" do
|
5
|
+
it "saves the consumer" do
|
6
|
+
provider = create_provider
|
7
|
+
consumer = provider.add_consumer("http://testconsumer.example.org/")
|
8
|
+
provider.find_consumer(consumer.shared_key).should == consumer
|
9
|
+
end
|
10
|
+
|
11
|
+
it "disallows a consumer with the same callback" do
|
12
|
+
provider = create_provider
|
13
|
+
provider.add_consumer("http://testconsumer.example.org/")
|
14
|
+
lambda { provider.add_consumer("http://testconsumer.example.org/") }.
|
15
|
+
should raise_error(OAuthProvider::DuplicateCallback)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "with a custom token" do
|
19
|
+
it "uses the token" do
|
20
|
+
provider = create_provider
|
21
|
+
consumer = provider.add_consumer("http://testconsumer.example.org/", OAuthProvider::Token.new("shared key", "secret key"))
|
22
|
+
provider.find_consumer("shared key").should == consumer
|
23
|
+
consumer.secret_key.should == "secret key"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "all the consumers" do
|
29
|
+
it "returns the complete list" do
|
30
|
+
provider = create_provider
|
31
|
+
one = provider.add_consumer("http://one.com/")
|
32
|
+
two = provider.add_consumer("http://two.com/")
|
33
|
+
provider.consumers.should == [one, two]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "deleting a consumer" do
|
38
|
+
it "removes the consumer from the backend" do
|
39
|
+
provider = create_provider
|
40
|
+
one = provider.add_consumer("http://one.com/")
|
41
|
+
provider.destroy_consumer(one)
|
42
|
+
provider.consumers.should be_empty
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "finding a consumer" do
|
47
|
+
it "removes the consumer from the backend" do
|
48
|
+
provider = create_provider
|
49
|
+
one = provider.add_consumer("http://one.com/")
|
50
|
+
provider.destroy_consumer(one)
|
51
|
+
provider.consumers.should be_empty
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "with a consumer" do
|
56
|
+
before(:each) do
|
57
|
+
@provider = create_provider
|
58
|
+
@consumer = @provider.add_consumer("http://testconsumer.example.org/")
|
59
|
+
@client = OAuthClient.new(@consumer)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "issues a user request" do
|
63
|
+
user_request = @provider.issue_request(@client.request)
|
64
|
+
lambda { @provider.confirm_access(@client.request(user_request)) }.
|
65
|
+
should raise_error(OAuthProvider::UserAccessNotFound)
|
66
|
+
@consumer.find_user_request(user_request.shared_key).should_not be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "with a user request" do
|
70
|
+
before(:each) do
|
71
|
+
@user_request = @provider.issue_request(@client.request)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "authorizes the request" do
|
75
|
+
@user_request.authorize
|
76
|
+
@user_request.should be_authorized
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "which has been authorized" do
|
80
|
+
before(:each) do
|
81
|
+
@user_request.authorize
|
82
|
+
end
|
83
|
+
|
84
|
+
it "upgrades the request" do
|
85
|
+
request = @client.request(@user_request)
|
86
|
+
user_access = @provider.upgrade_request(request)
|
87
|
+
lambda { @provider.confirm_access(@client.request(user_access)) }.
|
88
|
+
should_not raise_error
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "converted to user access" do
|
92
|
+
before(:each) do
|
93
|
+
request = @client.request(@user_request)
|
94
|
+
@access_token = @provider.upgrade_request(request)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "validates the token" do
|
98
|
+
request = @client.request(@access_token)
|
99
|
+
@provider.confirm_access(request)
|
100
|
+
lambda { @provider.confirm_access(request) }.
|
101
|
+
should_not raise_error
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'rack'
|
4
|
+
require 'pp'
|
5
|
+
require 'oauth/helper'
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/../lib/oauth_provider'
|
8
|
+
require File.dirname(__FILE__) + '/helpers/backend_helper'
|
9
|
+
|
10
|
+
OAuthBackendHelper.setup
|
11
|
+
|
12
|
+
module OAuthProviderHelper
|
13
|
+
def create_provider
|
14
|
+
OAuthBackendHelper.provider
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Spec::Runner.configure do |config|
|
19
|
+
config.include(OAuthProviderHelper)
|
20
|
+
|
21
|
+
config.before(:each) do
|
22
|
+
OAuthBackendHelper.reset
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'oauth/request_proxy/base'
|
27
|
+
|
28
|
+
module OAuth
|
29
|
+
module RequestProxy
|
30
|
+
class MockRequest < OAuth::RequestProxy::Base
|
31
|
+
proxies Hash
|
32
|
+
|
33
|
+
def parameters
|
34
|
+
@request["parameters"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def method
|
38
|
+
@request["method"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def uri
|
42
|
+
@request["uri"]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class OAuthClient
|
49
|
+
def initialize(consumer)
|
50
|
+
@consumer = OAuth::Consumer.new(consumer.shared_key, consumer.secret_key)
|
51
|
+
end
|
52
|
+
attr_reader :consumer
|
53
|
+
|
54
|
+
def request(token = nil)
|
55
|
+
Request.new(@consumer, Time.now.to_i, token).signed_request
|
56
|
+
end
|
57
|
+
|
58
|
+
class Request
|
59
|
+
include OAuth::Helper
|
60
|
+
|
61
|
+
def initialize(consumer, timestamp, token)
|
62
|
+
@consumer, @timestamp, @nonce, @token = consumer, timestamp, generate_key, token
|
63
|
+
end
|
64
|
+
|
65
|
+
def signed_request
|
66
|
+
r = request
|
67
|
+
r["parameters"]["oauth_signature"] = signature
|
68
|
+
r
|
69
|
+
end
|
70
|
+
|
71
|
+
def signature
|
72
|
+
OAuth::Signature.sign(request) do |token|
|
73
|
+
[@token && @token.secret_key, @consumer.secret]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def request
|
78
|
+
{"parameters" => query_hash,
|
79
|
+
"method" => "GET",
|
80
|
+
"uri" => "http://example.org/"}
|
81
|
+
end
|
82
|
+
|
83
|
+
def query_hash
|
84
|
+
h = {"oauth_nonce" => @nonce,
|
85
|
+
"oauth_timestamp" => @timestamp,
|
86
|
+
"oauth_signature_method" => "HMAC-SHA1",
|
87
|
+
"oauth_consumer_key" => @consumer.key,
|
88
|
+
"oauth_version" => "1.0"}
|
89
|
+
h["oauth_token"] = @token.shared_key if @token
|
90
|
+
h
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|