warden_oauth_provider 1.0.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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.textile +58 -0
- data/Rakefile +2 -0
- data/lib/generators/warden_oauth_provider/install/install_generator.rb +27 -0
- data/lib/generators/warden_oauth_provider/install/templates/migration.rb +45 -0
- data/lib/warden_oauth_provider.rb +19 -0
- data/lib/warden_oauth_provider/client_application.rb +21 -0
- data/lib/warden_oauth_provider/nonce.rb +15 -0
- data/lib/warden_oauth_provider/provider_strategy.rb +82 -0
- data/lib/warden_oauth_provider/token/access.rb +11 -0
- data/lib/warden_oauth_provider/token/base.rb +32 -0
- data/lib/warden_oauth_provider/token/request.rb +29 -0
- data/lib/warden_oauth_provider/token_strategy.rb +40 -0
- data/lib/warden_oauth_provider/version.rb +3 -0
- data/spec/access_token_spec.rb +214 -0
- data/spec/all_steps_spec.rb +79 -0
- data/spec/authorize_spec.rb +43 -0
- data/spec/client_application_spec.rb +41 -0
- data/spec/helpers/factories.rb +16 -0
- data/spec/helpers/request_helper.rb +87 -0
- data/spec/nonce_spec.rb +23 -0
- data/spec/oauth_request_spec.rb +161 -0
- data/spec/request_token_spec.rb +169 -0
- data/spec/spec_helper.rb +66 -0
- data/spec/token_spec.rb +222 -0
- data/spec/token_strategy_spec.rb +158 -0
- data/warden_oauth_provider.gemspec +27 -0
- metadata +214 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "OAuth all steps" do
|
4
|
+
|
5
|
+
context "Success" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
@client_application = Factory.create(:client_application)
|
9
|
+
@user = Factory(:user)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should succeed all oauth steps" do
|
13
|
+
|
14
|
+
# Step 1 - Request token
|
15
|
+
auth_str_step1 = oauth_header({
|
16
|
+
:realm => "MoneyBird",
|
17
|
+
:oauth_consumer_key => @client_application.key,
|
18
|
+
:oauth_signature_method => "PLAINTEXT",
|
19
|
+
:oauth_timestamp => Time.now.to_i+1,
|
20
|
+
:oauth_nonce => Time.now.to_f+1,
|
21
|
+
:oauth_callback => "oob",
|
22
|
+
:oauth_signature => @client_application.secret + "%26"
|
23
|
+
})
|
24
|
+
env_step1 = env_with_params("/oauth/request_token", {}, {
|
25
|
+
"HTTP_AUTHORIZATION" => auth_str_step1
|
26
|
+
})
|
27
|
+
response = setup_rack.call(env_step1)
|
28
|
+
response.first.should == 200
|
29
|
+
oauth_response = Hash[*response.last.first.split("&").collect { |v| v.split("=") }.flatten]
|
30
|
+
oauth_request_token = oauth_response["oauth_token"]
|
31
|
+
oauth_request_token_secret = oauth_response["oauth_token_secret"]
|
32
|
+
|
33
|
+
# Step 2 - Authorize
|
34
|
+
req = WardenOauthProvider::Token::Request.find_by_token(oauth_request_token)
|
35
|
+
env_step2 = env_with_params("/oauth/authorize", {:oauth_token => oauth_request_token, :username => "John"}, {})
|
36
|
+
response = setup_rack.call(env_step2)
|
37
|
+
response.first.should == 302
|
38
|
+
location = URI.parse(response[1]["Location"])
|
39
|
+
oauth_response = Hash[*location.query.split("&").collect { |v| v.split("=") }.flatten]
|
40
|
+
oauth_verifier = oauth_response["oauth_verifier"]
|
41
|
+
|
42
|
+
# Step 3 - Access token
|
43
|
+
auth_str_step3 = oauth_header({
|
44
|
+
:realm => "MoneyBird",
|
45
|
+
:oauth_consumer_key => @client_application.key,
|
46
|
+
:oauth_token => oauth_request_token,
|
47
|
+
:oauth_signature_method => "PLAINTEXT",
|
48
|
+
:oauth_timestamp => Time.now.to_i+2,
|
49
|
+
:oauth_nonce => Time.now.to_f+2,
|
50
|
+
:oauth_verifier => oauth_verifier,
|
51
|
+
:oauth_signature => @client_application.secret + "%26" + oauth_request_token_secret
|
52
|
+
})
|
53
|
+
env_step3 = env_with_params("/oauth/access_token", {}, {
|
54
|
+
"HTTP_AUTHORIZATION" => auth_str_step3
|
55
|
+
})
|
56
|
+
response = setup_rack.call(env_step3)
|
57
|
+
response.first.should == 200
|
58
|
+
oauth_response = Hash[*response.last.first.split("&").collect { |v| v.split("=") }.flatten]
|
59
|
+
oauth_access_token = oauth_response["oauth_token"]
|
60
|
+
oauth_access_token_secret = oauth_response["oauth_token_secret"]
|
61
|
+
|
62
|
+
# Step 4 - App request with access token
|
63
|
+
auth_str_step4 = oauth_header({
|
64
|
+
:realm => "MoneyBird",
|
65
|
+
:oauth_consumer_key => @client_application.key,
|
66
|
+
:oauth_token => oauth_access_token,
|
67
|
+
:oauth_signature_method => "PLAINTEXT",
|
68
|
+
:oauth_timestamp => Time.now.to_i+3,
|
69
|
+
:oauth_nonce => Time.now.to_f+3,
|
70
|
+
:oauth_signature => @client_application.secret + "%26" + oauth_access_token_secret
|
71
|
+
})
|
72
|
+
env_step4 = env_with_params("/invoices", {}, {
|
73
|
+
"HTTP_AUTHORIZATION" => auth_str_step4
|
74
|
+
})
|
75
|
+
response = setup_rack.call(env_step4)
|
76
|
+
response.first.should == 200
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Authorize" do
|
4
|
+
|
5
|
+
context "Success" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
@request_token = Factory.create(:request_token, :client_application => Factory.create(:client_application))
|
9
|
+
env = env_with_params("/oauth/authorize", {:oauth_token => @request_token.token, :username => "John"}, {})
|
10
|
+
@response = setup_rack.call(env)
|
11
|
+
@location = URI.parse(@response[1]["Location"])
|
12
|
+
@oauth_response = Hash[*@location.query.split("&").collect { |v| v.split("=") }.flatten]
|
13
|
+
@response.first.should == 302
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have an oauth token" do
|
17
|
+
@oauth_response.keys.should include("oauth_token")
|
18
|
+
@oauth_response["oauth_token"].should == @request_token.token
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have an oauth verifier" do
|
22
|
+
@oauth_response.keys.should include("oauth_verifier")
|
23
|
+
@oauth_response["oauth_verifier"].should_not be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have stored the oauth verifier in the database" do
|
27
|
+
WardenOauthProvider::Token::Request.where(:token => @oauth_response["oauth_token"], :verifier => @oauth_response["oauth_verifier"]).count.should == 1
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
context "Failure" do
|
33
|
+
it "should response with a 401 if the token is invalidated" do
|
34
|
+
@request_token = Factory.create(:request_token, :client_application => Factory.create(:client_application))
|
35
|
+
@request_token.invalidate!
|
36
|
+
|
37
|
+
env = env_with_params("/oauth/authorize", {:oauth_token => @request_token.token}, {})
|
38
|
+
@response = setup_rack.call(env)
|
39
|
+
@response.first.should == 401
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WardenOauthProvider::ClientApplication do
|
4
|
+
|
5
|
+
it "should be valid with valid attributes" do
|
6
|
+
@application = WardenOauthProvider::ClientApplication.create(:name => "Test application", :url => "http://testapp.com")
|
7
|
+
@application.should be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be invalid with invalid attributes" do
|
11
|
+
@application = WardenOauthProvider::ClientApplication.new
|
12
|
+
@application.valid?
|
13
|
+
@application.errors[:name].should_not be_empty
|
14
|
+
@application.errors[:url].should_not be_empty
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have key and secret" do
|
18
|
+
@application = WardenOauthProvider::ClientApplication.create(:name => "Test application", :url => "http://testapp.com")
|
19
|
+
@application.key.should_not be_nil
|
20
|
+
@application.secret.should_not be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
["http://valid.com",
|
24
|
+
"http://valid.com/path"].each do |url|
|
25
|
+
it "should allow #{url} as a valid url" do
|
26
|
+
@application = WardenOauthProvider::ClientApplication.new(:url => url)
|
27
|
+
@application.valid?
|
28
|
+
@application.errors[:url].should be_empty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
["ftp://invalid.com",
|
33
|
+
"http:://invalid.com"].each do |url|
|
34
|
+
it "should not allow #{url} as a valid url" do
|
35
|
+
@application = WardenOauthProvider::ClientApplication.new(:url => url)
|
36
|
+
@application.valid?
|
37
|
+
@application.errors[:url].should_not be_empty
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Factory.define(:client_application, :class => WardenOauthProvider::ClientApplication) do |f|
|
2
|
+
f.name "Example client application"
|
3
|
+
f.url "http://www.example.com"
|
4
|
+
f.support_url "http://www.example.com/support"
|
5
|
+
f.callback_url "http://www.example.com/callback"
|
6
|
+
end
|
7
|
+
|
8
|
+
Factory.define(:request_token, :class => WardenOauthProvider::Token::Request) do |f|
|
9
|
+
end
|
10
|
+
|
11
|
+
Factory.define(:access_token, :class => WardenOauthProvider::Token::Access) do |f|
|
12
|
+
end
|
13
|
+
|
14
|
+
Factory.define(:user) do |f|
|
15
|
+
f.name "John"
|
16
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module RequestHelper
|
2
|
+
|
3
|
+
Warden::Manager.serialize_into_session do |user|
|
4
|
+
user.id
|
5
|
+
end
|
6
|
+
|
7
|
+
Warden::Manager.serialize_from_session do |id|
|
8
|
+
User.find(id)
|
9
|
+
end
|
10
|
+
|
11
|
+
def env_with_params(path, params = {}, env = {})
|
12
|
+
method = params.delete(:method) || "GET"
|
13
|
+
env = { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => "#{method}" }.merge(env)
|
14
|
+
Rack::MockRequest.env_for("#{path}?#{Rack::Utils.build_query(params)}", env)
|
15
|
+
end
|
16
|
+
|
17
|
+
def oauth_header(params)
|
18
|
+
"OAuth #{params.collect { |k,v| "#{k}=\"#{v}\""}.join(", ") }"
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup_rack(app = nil, opts = {})
|
22
|
+
app ||= default_app
|
23
|
+
|
24
|
+
# Strategy used for authenticating the user to the app without oauth
|
25
|
+
# Required for authorize call to the app
|
26
|
+
Warden::Strategies.add(:success) do
|
27
|
+
def valid?
|
28
|
+
!params["username"].nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def authenticate!
|
32
|
+
if u = User.where(:name => params["username"]).first
|
33
|
+
success!(u)
|
34
|
+
else
|
35
|
+
fail!("User unknown")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
opts[:failure_app] ||= failure_app
|
41
|
+
opts[:default_strategies] ||= [:oauth_provider, :success]
|
42
|
+
opts[:oauth_request_token_path] ||= "/oauth/request_token"
|
43
|
+
opts[:oauth_access_token_path] ||= "/oauth/access_token"
|
44
|
+
|
45
|
+
Rack::Builder.new do
|
46
|
+
use opts[:session] || RequestHelper::Session
|
47
|
+
use Warden::Manager, opts
|
48
|
+
run app
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_app
|
53
|
+
lambda do |env|
|
54
|
+
env['warden'].authenticate!
|
55
|
+
request = Rack::Request.new(env)
|
56
|
+
if request.path =~ /^\/oauth\/authorize/
|
57
|
+
if env['warden'].authenticate?(:oauth_token, :scope => :oauth_token)
|
58
|
+
[302, {"Location" => env['oauth.redirect_url']}, []]
|
59
|
+
else
|
60
|
+
[401, {"Content-Type" => "text/plain"}, ["Token invalid"]]
|
61
|
+
end
|
62
|
+
else
|
63
|
+
[200, {"Content-Type" => "text/plain"}, ["Very secret resource!"]]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def failure_app
|
69
|
+
lambda do |env|
|
70
|
+
[401, {"Content-Type" => "text/plain"}, ["You Fail! #{env['warden'].message}"]]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
class Session
|
76
|
+
attr_accessor :app
|
77
|
+
def initialize(app,configs = {})
|
78
|
+
@app = app
|
79
|
+
end
|
80
|
+
|
81
|
+
def call(e)
|
82
|
+
e['rack.session'] ||= {}
|
83
|
+
@app.call(e)
|
84
|
+
end
|
85
|
+
end # session
|
86
|
+
|
87
|
+
end
|
data/spec/nonce_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WardenOauthProvider::Nonce do
|
4
|
+
before(:each) do
|
5
|
+
@oauth_nonce = WardenOauthProvider::Nonce.remember(Time.now.to_f, Time.now.to_i)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should be valid" do
|
9
|
+
@oauth_nonce.should be_valid
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not have errors" do
|
13
|
+
@oauth_nonce.errors.full_messages.should == []
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should not be a new record" do
|
17
|
+
@oauth_nonce.should_not be_new_record
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not allow a second one with the same values" do
|
21
|
+
WardenOauthProvider::Nonce.remember(@oauth_nonce.nonce, @oauth_nonce.timestamp).should == false
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "OAuth request" do
|
4
|
+
|
5
|
+
context "Success" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
@user = Factory(:user)
|
9
|
+
@client_application = Factory.create(:client_application)
|
10
|
+
@access_token = Factory.create(:access_token, :user => @user, :client_application => @client_application)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should allow to access very secret resources" do
|
14
|
+
auth_str = oauth_header({
|
15
|
+
:realm => "MoneyBird",
|
16
|
+
:oauth_consumer_key => @client_application.key,
|
17
|
+
:oauth_token => @access_token.token,
|
18
|
+
:oauth_signature_method => "PLAINTEXT",
|
19
|
+
:oauth_timestamp => Time.now.to_i,
|
20
|
+
:oauth_nonce => Time.now.to_f,
|
21
|
+
:oauth_signature => @client_application.secret + "%26" + @access_token.secret
|
22
|
+
})
|
23
|
+
|
24
|
+
env = env_with_params("/invoices", {}, {
|
25
|
+
"HTTP_AUTHORIZATION" => auth_str
|
26
|
+
})
|
27
|
+
@response = setup_rack.call(env)
|
28
|
+
@response.first.should == 200
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context "Success (GET)" do
|
34
|
+
|
35
|
+
before(:all) do
|
36
|
+
@user = Factory(:user)
|
37
|
+
@client_application = Factory.create(:client_application)
|
38
|
+
@access_token = Factory.create(:access_token, :user => @user, :client_application => @client_application)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should allow to access very secret resources" do
|
42
|
+
auth_params = {
|
43
|
+
:realm => "MoneyBird",
|
44
|
+
:oauth_consumer_key => @client_application.key,
|
45
|
+
:oauth_token => @access_token.token,
|
46
|
+
:oauth_signature_method => "PLAINTEXT",
|
47
|
+
:oauth_timestamp => Time.now.to_i,
|
48
|
+
:oauth_nonce => Time.now.to_f,
|
49
|
+
:oauth_signature => @client_application.secret + "&" + @access_token.secret
|
50
|
+
}
|
51
|
+
|
52
|
+
env = env_with_params("/invoices", auth_params)
|
53
|
+
@response = setup_rack.call(env)
|
54
|
+
@response.first.should == 200
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
context "Failure" do
|
60
|
+
|
61
|
+
before(:all) do
|
62
|
+
@user = Factory(:user)
|
63
|
+
@client_application = Factory.create(:client_application)
|
64
|
+
@access_token = Factory.create(:access_token, :user => @user, :client_application => @client_application)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should not allow to access very secret resources if the second request contains the same nonce" do
|
68
|
+
auth_str = oauth_header({
|
69
|
+
:realm => "MoneyBird",
|
70
|
+
:oauth_consumer_key => @client_application.key,
|
71
|
+
:oauth_token => @access_token.token,
|
72
|
+
:oauth_signature_method => "PLAINTEXT",
|
73
|
+
:oauth_timestamp => Time.now.to_i,
|
74
|
+
:oauth_nonce => Time.now.to_f,
|
75
|
+
:oauth_signature => @client_application.secret + "%26" + @access_token.secret
|
76
|
+
})
|
77
|
+
env1 = env_with_params("/invoices", {}, {
|
78
|
+
"HTTP_AUTHORIZATION" => auth_str
|
79
|
+
})
|
80
|
+
env2 = env_with_params("/invoices", {}, {
|
81
|
+
"HTTP_AUTHORIZATION" => auth_str
|
82
|
+
})
|
83
|
+
|
84
|
+
@response1 = setup_rack.call(env1)
|
85
|
+
@response2 = setup_rack.call(env2)
|
86
|
+
@response2.first.should == 401
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should not allow to access very secret resources with invalid consumer key" do
|
90
|
+
auth_str = oauth_header({
|
91
|
+
:realm => "MoneyBird",
|
92
|
+
:oauth_consumer_key => @client_application.key + "invalid",
|
93
|
+
:oauth_token => @access_token.token,
|
94
|
+
:oauth_signature_method => "PLAINTEXT",
|
95
|
+
:oauth_timestamp => Time.now.to_i,
|
96
|
+
:oauth_nonce => Time.now.to_f,
|
97
|
+
:oauth_signature => @client_application.secret + "%26" + @access_token.secret
|
98
|
+
})
|
99
|
+
|
100
|
+
env = env_with_params("/invoices", {}, {
|
101
|
+
"HTTP_AUTHORIZATION" => auth_str
|
102
|
+
})
|
103
|
+
@response = setup_rack.call(env)
|
104
|
+
@response.first.should == 401
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should not allow to access very secret resources with invalid token" do
|
108
|
+
auth_str = oauth_header({
|
109
|
+
:realm => "MoneyBird",
|
110
|
+
:oauth_consumer_key => @client_application.key,
|
111
|
+
:oauth_token => @access_token.token + "invalid",
|
112
|
+
:oauth_signature_method => "PLAINTEXT",
|
113
|
+
:oauth_timestamp => Time.now.to_i,
|
114
|
+
:oauth_nonce => Time.now.to_f,
|
115
|
+
:oauth_signature => @client_application.secret + "%26" + @access_token.secret
|
116
|
+
})
|
117
|
+
|
118
|
+
env = env_with_params("/invoices", {}, {
|
119
|
+
"HTTP_AUTHORIZATION" => auth_str
|
120
|
+
})
|
121
|
+
@response = setup_rack.call(env)
|
122
|
+
@response.first.should == 401
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should not allow to access very secret resources with invalid signature" do
|
126
|
+
auth_str = oauth_header({
|
127
|
+
:realm => "MoneyBird",
|
128
|
+
:oauth_consumer_key => @client_application.key,
|
129
|
+
:oauth_token => @access_token.token,
|
130
|
+
:oauth_signature_method => "PLAINTEXT",
|
131
|
+
:oauth_timestamp => Time.now.to_i,
|
132
|
+
:oauth_nonce => Time.now.to_f,
|
133
|
+
:oauth_signature => @client_application.secret + "%26" + @access_token.secret + "invalid"
|
134
|
+
})
|
135
|
+
|
136
|
+
env = env_with_params("/invoices", {}, {
|
137
|
+
"HTTP_AUTHORIZATION" => auth_str
|
138
|
+
})
|
139
|
+
@response = setup_rack.call(env)
|
140
|
+
@response.first.should == 401
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should not allow to access very secret resources with invalid keys" do
|
144
|
+
auth_str = oauth_header({
|
145
|
+
:realm => "MoneyBird",
|
146
|
+
:oauth_consumer_key => @client_application.key + "invalid",
|
147
|
+
:oauth_token => @access_token.token + "invalid",
|
148
|
+
:oauth_signature_method => "PLAINTEXT",
|
149
|
+
:oauth_timestamp => Time.now.to_i,
|
150
|
+
:oauth_nonce => Time.now.to_f,
|
151
|
+
:oauth_signature => @client_application.secret + "%26" + @access_token.secret + "invalid"
|
152
|
+
})
|
153
|
+
|
154
|
+
env = env_with_params("/invoices", {}, {
|
155
|
+
"HTTP_AUTHORIZATION" => auth_str
|
156
|
+
})
|
157
|
+
@response = setup_rack.call(env)
|
158
|
+
@response.first.should == 401
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|