ey_api_hmac 0.0.1
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/Gemfile +10 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +19 -0
- data/Rakefile +10 -0
- data/ey_api_hmac.gemspec +23 -0
- data/lib/ey_api_hmac.rb +94 -0
- data/lib/ey_api_hmac/api_auth.rb +49 -0
- data/lib/ey_api_hmac/base_connection.rb +103 -0
- data/lib/ey_api_hmac/version.rb +5 -0
- data/spec/api_auth_spec.rb +131 -0
- data/spec/sso_spec.rb +62 -0
- metadata +106 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ey_api_hmac (0.0.1)
|
5
|
+
rack-client
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.1.2)
|
11
|
+
halorgium-auth-hmac (1.1.1.2010100601)
|
12
|
+
rack (1.3.2)
|
13
|
+
rack-client (0.4.0)
|
14
|
+
rack (>= 1.0.0)
|
15
|
+
rake (0.9.2)
|
16
|
+
rspec (2.6.0)
|
17
|
+
rspec-core (~> 2.6.0)
|
18
|
+
rspec-expectations (~> 2.6.0)
|
19
|
+
rspec-mocks (~> 2.6.0)
|
20
|
+
rspec-core (2.6.4)
|
21
|
+
rspec-expectations (2.6.0)
|
22
|
+
diff-lcs (~> 1.1.2)
|
23
|
+
rspec-mocks (2.6.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
ey_api_hmac!
|
30
|
+
halorgium-auth-hmac
|
31
|
+
rake
|
32
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Engine Yard
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
data/ey_api_hmac.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ey_api_hmac/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ey_api_hmac"
|
7
|
+
s.version = EY::ApiHMAC::VERSION
|
8
|
+
s.authors = ["Jacob Burkhart & Thorben Schröder & David Calavera & others"]
|
9
|
+
s.email = ["jacob@engineyard.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{HMAC Rack basic implementation for Engine Yard services}
|
12
|
+
s.description = %q{basic wrapper for rack-client + middlewares for HMAC auth + helpers for SSO auth}
|
13
|
+
|
14
|
+
s.rubyforge_project = "ey_api_hmac"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'rack-client'
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
end
|
data/lib/ey_api_hmac.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'ey_api_hmac/base_connection'
|
2
|
+
require 'ey_api_hmac/api_auth'
|
3
|
+
|
4
|
+
module EY
|
5
|
+
module ApiHMAC
|
6
|
+
require 'openssl'
|
7
|
+
|
8
|
+
def self.sign_for_sso(url, parameters, auth_id, auth_key)
|
9
|
+
uri = URI.parse(url)
|
10
|
+
raise ArgumentError, "use parameters argument, got query: '#{uri.query}'" if uri.query
|
11
|
+
uri.query = parameters.sort.map {|e| e.map{|str| CGI.escape(str.to_s)}.join '='}.join '&'
|
12
|
+
signature = CGI.escape(base64digest(uri.query.to_s, auth_key))
|
13
|
+
uri.query += "&signature=#{signature}"
|
14
|
+
uri.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.verify_for_sso(url, auth_id, auth_key)
|
18
|
+
uri = URI.parse(url)
|
19
|
+
signature = CGI.unescape(uri.query.match(/&signature=(.*)$/)[1])
|
20
|
+
signed_string = uri.query.gsub(/&signature=(.*)$/,"")
|
21
|
+
base64digest(signed_string.to_s, auth_key) == signature
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.sign!(env, key_id, secret, strict = false)
|
25
|
+
env["HTTP_AUTHORIZATION"] = "AuthHMAC #{key_id}:#{signature(env, secret, strict)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.canonical_string(env, strict = false)
|
29
|
+
parts = []
|
30
|
+
adder = Proc.new do |var|
|
31
|
+
unless env[var]
|
32
|
+
raise HmacAuthFail, "'#{var}' header missing and required in #{env.inspect}"
|
33
|
+
end
|
34
|
+
parts << env[var]
|
35
|
+
end
|
36
|
+
adder["REQUEST_METHOD"]
|
37
|
+
adder["CONTENT_TYPE"]
|
38
|
+
if env["HTTP_CONTENT_MD5"]
|
39
|
+
adder["HTTP_CONTENT_MD5"]
|
40
|
+
else
|
41
|
+
parts << generated_md5(env)
|
42
|
+
end
|
43
|
+
adder["HTTP_DATE"]
|
44
|
+
adder["PATH_INFO"]
|
45
|
+
parts.join("\n")
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.signature(env, secret, strict = false)
|
49
|
+
base64digest(canonical_string(env, strict), secret)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.base64digest(data,secret)
|
53
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
54
|
+
[OpenSSL::HMAC.digest(digest, secret, data)].pack('m').strip
|
55
|
+
end
|
56
|
+
|
57
|
+
class HmacAuthFail < StandardError; end
|
58
|
+
|
59
|
+
def self.authenticate!(env, &lookup)
|
60
|
+
rx = Regexp.new("AuthHMAC ([^:]+):(.+)$")
|
61
|
+
if md = rx.match(env["HTTP_AUTHORIZATION"])
|
62
|
+
access_key_id = md[1]
|
63
|
+
hmac = md[2]
|
64
|
+
secret = lookup.call(access_key_id)
|
65
|
+
unless secret
|
66
|
+
raise HmacAuthFail, "couldn't find auth for #{access_key_id}"
|
67
|
+
end
|
68
|
+
unless hmac == signature(env, secret)
|
69
|
+
raise HmacAuthFail, "signature mismatch"
|
70
|
+
end
|
71
|
+
else
|
72
|
+
raise HmacAuthFail, "no authorization header"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.authenticated?(env, &lookup)
|
77
|
+
begin
|
78
|
+
authenticate!(env, &lookup)
|
79
|
+
true
|
80
|
+
rescue HmacAuthFail => e
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def self.generated_md5(env)
|
88
|
+
request_body = env["rack.input"].read
|
89
|
+
env["rack.input"].rewind
|
90
|
+
OpenSSL::Digest::MD5.hexdigest(request_body)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module EY
|
2
|
+
module ApiHMAC
|
3
|
+
module ApiAuth
|
4
|
+
CONSUMER = "ey_api_hmac.consumer_id"
|
5
|
+
|
6
|
+
#a Server middleware to validate requests, setup with a block to lookup the auth_key based on auth_id
|
7
|
+
class LookupServer
|
8
|
+
def initialize(app, &lookup)
|
9
|
+
@app, @lookup = app, lookup
|
10
|
+
end
|
11
|
+
|
12
|
+
#TODO: rescue HmacAuthFail and return 403?
|
13
|
+
def call(env)
|
14
|
+
ApiHMAC.authenticate!(env) do |auth_id|
|
15
|
+
@lookup.call(env, auth_id)
|
16
|
+
end
|
17
|
+
@app.call(env)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
#a Server middleware to validate requests, setup with a class that responds_to :find_by_auth_id, :id, :auth_key
|
22
|
+
class Server < LookupServer
|
23
|
+
def initialize(app, klass)
|
24
|
+
lookup = Proc.new do |env, auth_id|
|
25
|
+
if consumer = klass.find_by_auth_id(auth_id)
|
26
|
+
env[CONSUMER] = consumer.id
|
27
|
+
consumer.auth_key
|
28
|
+
else
|
29
|
+
raise "no #{klass} consumer #{auth_id.inspect}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
super(app, &lookup)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#the Client middleware that's used to add authentication to requests
|
37
|
+
class Client
|
38
|
+
def initialize(app, auth_id, auth_key)
|
39
|
+
@app, @auth_id, @auth_key = app, auth_id, auth_key
|
40
|
+
end
|
41
|
+
def call(env)
|
42
|
+
ApiHMAC.sign!(env, @auth_id, @auth_key)
|
43
|
+
@app.call(env)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'rack/client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module EY
|
5
|
+
module ApiHMAC
|
6
|
+
class BaseConnection
|
7
|
+
attr_reader :auth_id, :auth_key
|
8
|
+
|
9
|
+
def initialize(auth_id, auth_key, user_agent = nil)
|
10
|
+
@auth_id = auth_id
|
11
|
+
@auth_key = auth_key
|
12
|
+
@standard_headers = {
|
13
|
+
'CONTENT_TYPE' => 'application/json',
|
14
|
+
'Accept'=> 'application/json',
|
15
|
+
"Date" => Time.now.to_s, #TODO: what is the right way to format this header?
|
16
|
+
'USER_AGENT' => user_agent || default_user_agent
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
class NotFound < StandardError
|
21
|
+
def initialize(url)
|
22
|
+
super("#{url} not found")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ValidationError < StandardError
|
27
|
+
attr_reader :error_messages
|
28
|
+
|
29
|
+
def initialize(response)
|
30
|
+
json_response = JSON.parse(response.body)
|
31
|
+
@error_messages = json_response["error_messages"]
|
32
|
+
super("error: #{@error_messages.join("\n")}")
|
33
|
+
rescue => e
|
34
|
+
@error_messages = []
|
35
|
+
super("error: #{response.body}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class UnknownError < StandardError
|
40
|
+
def initialize(response)
|
41
|
+
super("unknown error(#{response.status}): #{response.body}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_writer :backend
|
46
|
+
def backend
|
47
|
+
@backend ||= Rack::Client::Handler::NetHTTP
|
48
|
+
end
|
49
|
+
|
50
|
+
def post(url, body, &block)
|
51
|
+
request(:post, url, body, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def put(url, body, &block)
|
55
|
+
request(:put, url, body, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete(url, &block)
|
59
|
+
request(:delete, url, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def get(url, &block)
|
63
|
+
request(:get, url, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def client
|
69
|
+
bak = self.backend
|
70
|
+
#damn you scope!
|
71
|
+
auth_id_arg = auth_id
|
72
|
+
auth_key_arg = auth_key
|
73
|
+
@client ||= Rack::Client.new do
|
74
|
+
use EY::ApiHMAC::ApiAuth::Client, auth_id_arg, auth_key_arg
|
75
|
+
run bak
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def request(method, url, body = nil, &block)
|
80
|
+
if body
|
81
|
+
response = client.send(method, url, @standard_headers, body.to_json)
|
82
|
+
else
|
83
|
+
response = client.send(method, url, @standard_headers)
|
84
|
+
end
|
85
|
+
handle_response(url, response, &block)
|
86
|
+
end
|
87
|
+
|
88
|
+
def handle_response(url, response)
|
89
|
+
case response.status
|
90
|
+
when 200, 201
|
91
|
+
json_body = JSON.parse(response.body)
|
92
|
+
yield json_body, response["Location"] if block_given?
|
93
|
+
when 404
|
94
|
+
raise NotFound.new(url)
|
95
|
+
when 400
|
96
|
+
raise ValidationError.new(response)
|
97
|
+
else
|
98
|
+
raise UnknownError.new(response)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'ey_api_hmac'
|
2
|
+
require 'auth-hmac'
|
3
|
+
|
4
|
+
describe EY::ApiHMAC::ApiAuth do
|
5
|
+
|
6
|
+
#TODO: reject requests with old dates?
|
7
|
+
|
8
|
+
describe "AuthHMAC working" do
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@env = {'PATH_INFO' => "/path/to/put",
|
12
|
+
'QUERY_STRING' => 'foo=bar&bar=foo',
|
13
|
+
'CONTENT_TYPE' => 'text/plain',
|
14
|
+
'HTTP_CONTENT_MD5' => 'blahblah',
|
15
|
+
'REQUEST_METHOD' => "PUT",
|
16
|
+
'HTTP_DATE' => "Thu, 10 Jul 2008 03:29:56 GMT",
|
17
|
+
"rack.input" => StringIO.new}
|
18
|
+
@request = Rack::Request.new(@env)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".canonical_string" do
|
22
|
+
it "should generate a canonical string using default method" do
|
23
|
+
expected = "PUT\ntext/plain\nblahblah\nThu, 10 Jul 2008 03:29:56 GMT\n/path/to/put"
|
24
|
+
AuthHMAC.canonical_string(@request).should == expected
|
25
|
+
EY::ApiHMAC.canonical_string(@env).should == expected
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".signature" do
|
30
|
+
it "should generate a valid signature string for a secret" do
|
31
|
+
expected = "71wAJM4IIu/3o6lcqx/tw7XnAJs="
|
32
|
+
AuthHMAC.signature(@request, 'secret').should == expected
|
33
|
+
EY::ApiHMAC.signature(@env, 'secret').should == expected
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "sign!" do
|
38
|
+
before do
|
39
|
+
@expected = "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
|
40
|
+
end
|
41
|
+
|
42
|
+
it "signs as expected with AuthHMAC" do
|
43
|
+
AuthHMAC.sign!(@request, "my-key-id", "secret")
|
44
|
+
@request['Authorization'].should == @expected
|
45
|
+
end
|
46
|
+
|
47
|
+
it "signs as expected with ApiAuth" do
|
48
|
+
EY::ApiHMAC.sign!(@env, 'my-key-id', 'secret')
|
49
|
+
@env["HTTP_AUTHORIZATION"].should == @expected
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "authenticated?" do
|
55
|
+
describe "request signed by AuthHMAC" do
|
56
|
+
before do
|
57
|
+
AuthHMAC.sign!(@request, 'access key 1', 'secret')
|
58
|
+
@env["HTTP_AUTHORIZATION"] = @request["Authorization"]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "verifies by ApiAuth" do
|
62
|
+
@lookup = Proc.new{ |key| 'secret' if key == 'access key 1' }
|
63
|
+
EY::ApiHMAC.authenticated?(@env, &@lookup).should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "verifies by AuthHMAC" do
|
67
|
+
@authhmac = AuthHMAC.new({"access key 1" => 'secret'})
|
68
|
+
@authhmac.authenticated?(@request).should be_true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
describe "request signed by ApiAuth" do
|
72
|
+
before do
|
73
|
+
EY::ApiHMAC.sign!(@env, 'access key 1', 'secret')
|
74
|
+
end
|
75
|
+
|
76
|
+
it "verifies by ApiAuth" do
|
77
|
+
@lookup = Proc.new{ |key| 'secret' if key == 'access key 1' }
|
78
|
+
EY::ApiHMAC.authenticated?(@env, &@lookup).should be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
it "verifies by AuthHMAC" do
|
82
|
+
@authhmac = AuthHMAC.new({"access key 1" => 'secret'})
|
83
|
+
@authhmac.authenticated?(@request).should be_true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "without CONTENT_MD5" do
|
91
|
+
before do
|
92
|
+
@env = {'PATH_INFO' => "/path/to/put",
|
93
|
+
'QUERY_STRING' => 'foo=bar&bar=foo',
|
94
|
+
'CONTENT_TYPE' => 'text/plain',
|
95
|
+
'REQUEST_METHOD' => "PUT",
|
96
|
+
'HTTP_DATE' => "Thu, 10 Jul 2008 03:29:56 GMT",
|
97
|
+
"rack.input" => StringIO.new("something, something?")}
|
98
|
+
@request = Rack::Request.new(@env)
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "sign!" do
|
102
|
+
before do
|
103
|
+
@expected = "AuthHMAC my-key-id:YzKgetuk8Tkz19c4eUqbfg4QrFg="
|
104
|
+
end
|
105
|
+
|
106
|
+
it "signs as expected with AuthHMAC" do
|
107
|
+
AuthHMAC.sign!(@request, "my-key-id", "secret")
|
108
|
+
@request['Authorization'].should == @expected
|
109
|
+
end
|
110
|
+
|
111
|
+
it "signs as expected with ApiAuth" do
|
112
|
+
EY::ApiHMAC.sign!(@env, 'my-key-id', 'secret')
|
113
|
+
@env["HTTP_AUTHORIZATION"].should == @expected
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
it "complains when there is no HTTP_DATE" do
|
121
|
+
env = {'PATH_INFO' => "/path/to/put",
|
122
|
+
'QUERY_STRING' => 'foo=bar&bar=foo',
|
123
|
+
'CONTENT_TYPE' => 'text/plain',
|
124
|
+
'REQUEST_METHOD' => "PUT",
|
125
|
+
"rack.input" => StringIO.new}
|
126
|
+
lambda{
|
127
|
+
EY::ApiHMAC.sign!(env, 'my-key-id', 'secret', true)
|
128
|
+
}.should raise_error(/'HTTP_DATE' header missing and required/)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
data/spec/sso_spec.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'ey_api_hmac'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
|
5
|
+
describe EY::ApiHMAC do
|
6
|
+
|
7
|
+
describe "SSO" do
|
8
|
+
before do
|
9
|
+
@url = 'http://example.com/sign_test'
|
10
|
+
@parameters = {
|
11
|
+
"foo" => "bar",
|
12
|
+
"zarg" => "boot",
|
13
|
+
"xargs" => 5
|
14
|
+
}
|
15
|
+
@auth_id = "3243afed3242"
|
16
|
+
@auth_key = "987a87c98f78d9a8c798f7d89"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can sign sso calls" do
|
20
|
+
signed_url = EY::ApiHMAC.sign_for_sso(@url, @parameters, @auth_id, @auth_key)
|
21
|
+
uri = URI.parse(signed_url)
|
22
|
+
|
23
|
+
uri.scheme.should eq 'http'
|
24
|
+
uri.host.should eq 'example.com'
|
25
|
+
uri.path.should eq '/sign_test'
|
26
|
+
|
27
|
+
parameters = CGI::parse(uri.query)
|
28
|
+
parameters["signature"].first.should eq EY::ApiHMAC.base64digest("foo=bar&xargs=5&zarg=boot", @auth_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can verify signed requests" do
|
32
|
+
signed_url = EY::ApiHMAC.sign_for_sso(@url, @parameters, @auth_id, @auth_key)
|
33
|
+
EY::ApiHMAC.verify_for_sso(signed_url, @auth_id, @auth_key).should be_true
|
34
|
+
EY::ApiHMAC.verify_for_sso(signed_url + 'a', @auth_id, @auth_key).should be_false
|
35
|
+
end
|
36
|
+
|
37
|
+
#TODO: write a test that fails if we skip the CGI.unescape
|
38
|
+
|
39
|
+
#TODO: provide signature methods
|
40
|
+
|
41
|
+
#TODO: test that you get an error when you try to sign a url with any of the "Reserved" parameters (signature or timestamp)
|
42
|
+
|
43
|
+
#TODO: send the auth_id in the params too
|
44
|
+
|
45
|
+
#TODO: Rename "signature" to "ey_api_sso_hmac_signature"
|
46
|
+
|
47
|
+
#TODO: provide a time
|
48
|
+
|
49
|
+
#TODO: maybe an expiry time would be better
|
50
|
+
|
51
|
+
#TODO: should the other params be part of the gem?
|
52
|
+
# ey_user_id – the unique identifier for the user.
|
53
|
+
# ey_user_name – the full name of the user in plain text. Example: “John Doe”.
|
54
|
+
# access_level – either “owner” or “collaborator”.
|
55
|
+
# ey_return_to_url – the url to be used when sending the user back to EY.
|
56
|
+
# timestamp – time the signature was calculated, URL should be considered invalid is timestamp is more than 5 minutes off.
|
57
|
+
# signature_method – hash method used to generate the signature
|
58
|
+
# signature – HMAC digest of the other parameters and url (using the API secret)
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ey_api_hmac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- "Jacob Burkhart & Thorben Schr\xC3\xB6der & David Calavera & others"
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-15 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rack-client
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: basic wrapper for rack-client + middlewares for HMAC auth + helpers for SSO auth
|
50
|
+
email:
|
51
|
+
- jacob@engineyard.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- Gemfile
|
60
|
+
- Gemfile.lock
|
61
|
+
- LICENSE
|
62
|
+
- Rakefile
|
63
|
+
- ey_api_hmac.gemspec
|
64
|
+
- lib/ey_api_hmac.rb
|
65
|
+
- lib/ey_api_hmac/api_auth.rb
|
66
|
+
- lib/ey_api_hmac/base_connection.rb
|
67
|
+
- lib/ey_api_hmac/version.rb
|
68
|
+
- spec/api_auth_spec.rb
|
69
|
+
- spec/sso_spec.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: ""
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project: ey_api_hmac
|
100
|
+
rubygems_version: 1.5.2
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: HMAC Rack basic implementation for Engine Yard services
|
104
|
+
test_files:
|
105
|
+
- spec/api_auth_spec.rb
|
106
|
+
- spec/sso_spec.rb
|