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
@@ -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
|