kensa 0.4.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/.gitignore +2 -0
- data/Rakefile +34 -0
- data/TODO +8 -0
- data/a-server.rb +21 -0
- data/bin/kensa +153 -0
- data/kensa.gemspec +87 -0
- data/lib/heroku/kensa.rb +589 -0
- data/server.rb +13 -0
- data/set-env.sh +4 -0
- data/test/deprovision_check.rb +36 -0
- data/test/helper.rb +48 -0
- data/test/manifest_check_test.rb +142 -0
- data/test/provision_check_test.rb +50 -0
- data/test/provision_response_check_test.rb +72 -0
- data/test/resources/test_server.rb +43 -0
- data/test/sso_check_test.rb +28 -0
- metadata +189 -0
data/server.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'yajl'
|
3
|
+
require 'restclient'
|
4
|
+
|
5
|
+
post "/heroku/resources" do
|
6
|
+
resp = { :id => 123, :config => { "FOO" => "bar" } }
|
7
|
+
#resp = { :id => 123 }
|
8
|
+
Yajl::Encoder.encode(resp)
|
9
|
+
end
|
10
|
+
|
11
|
+
delete "/heroku/resources/:id" do
|
12
|
+
"ok"
|
13
|
+
end
|
data/set-env.sh
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/helper"
|
2
|
+
|
3
|
+
class DeprovisionCheckTest < Test::Unit::TestCase
|
4
|
+
include Heroku::Sensei
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@data = Manifest.skeleton.merge :id => 123
|
8
|
+
@responses = [
|
9
|
+
[200, ""],
|
10
|
+
[401, ""],
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
def check ; DeprovisionCheck ; end
|
15
|
+
|
16
|
+
test "valid on 200" do
|
17
|
+
assert_valid do |check|
|
18
|
+
stub :delete, check, @responses
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
test "status other than 200" do
|
23
|
+
@responses[0] = [500, ""]
|
24
|
+
assert_invalid do |check|
|
25
|
+
stub :delete, check, @responses
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
test "runs auth check" do
|
30
|
+
@responses[1] = [200, ""]
|
31
|
+
assert_invalid do |check|
|
32
|
+
stub :delete, check, @responses
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'heroku/kensa'
|
2
|
+
require 'contest'
|
3
|
+
|
4
|
+
class Test::Unit::TestCase
|
5
|
+
|
6
|
+
def assert_valid(data=@data, &blk)
|
7
|
+
check = create_check(data, &blk)
|
8
|
+
assert check.call
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_invalid(data=@data, &blk)
|
12
|
+
check = create_check(data, &blk)
|
13
|
+
assert !check.call
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_check(data, &blk)
|
17
|
+
check = self.check.new(data)
|
18
|
+
blk.call(check) if blk
|
19
|
+
check
|
20
|
+
end
|
21
|
+
|
22
|
+
module Headerize
|
23
|
+
attr_accessor :headers
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_json(data, headers={})
|
27
|
+
body = Yajl::Encoder.encode(data)
|
28
|
+
add_headers(body, headers)
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_headers(o, headers={})
|
32
|
+
o.extend Headerize
|
33
|
+
o.headers = {}
|
34
|
+
o.headers["Content-Type"] ||= "application/json"
|
35
|
+
o.headers.merge!(headers)
|
36
|
+
o
|
37
|
+
end
|
38
|
+
|
39
|
+
def stub(meth, o, returns)
|
40
|
+
o.instance_eval { @returns = Array(returns) }
|
41
|
+
eval <<-EVAL
|
42
|
+
def o.#{meth}(*args)
|
43
|
+
@returns.shift || fail("Nothing else to return from stub'ed method")
|
44
|
+
end
|
45
|
+
EVAL
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/helper"
|
2
|
+
|
3
|
+
class ManifestCheckTest < Test::Unit::TestCase
|
4
|
+
include Heroku::Sensei
|
5
|
+
|
6
|
+
def check ; ManifestCheck ; end
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@data = Manifest.skeleton
|
10
|
+
@data["plans"] << {
|
11
|
+
"id" => "advanced",
|
12
|
+
"name" => "Advanced",
|
13
|
+
"price" => "100",
|
14
|
+
"price_unit" => "month"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
test "is valid if no errors" do
|
19
|
+
assert_valid
|
20
|
+
end
|
21
|
+
|
22
|
+
test "has an id" do
|
23
|
+
@data.delete("id")
|
24
|
+
assert_invalid
|
25
|
+
end
|
26
|
+
|
27
|
+
test "has a name" do
|
28
|
+
@data.delete("name")
|
29
|
+
assert_invalid
|
30
|
+
end
|
31
|
+
|
32
|
+
test "api key exists" do
|
33
|
+
@data.delete("api")
|
34
|
+
assert_invalid
|
35
|
+
end
|
36
|
+
|
37
|
+
test "api is a Hash" do
|
38
|
+
@data["api"] = ""
|
39
|
+
assert_invalid
|
40
|
+
end
|
41
|
+
|
42
|
+
test "api has a username" do
|
43
|
+
@data["api"].delete("username")
|
44
|
+
assert_invalid
|
45
|
+
end
|
46
|
+
|
47
|
+
test "api has a password" do
|
48
|
+
@data["api"].delete("password")
|
49
|
+
assert_invalid
|
50
|
+
end
|
51
|
+
|
52
|
+
test "api contains test" do
|
53
|
+
@data["api"].delete("test")
|
54
|
+
assert_invalid
|
55
|
+
end
|
56
|
+
|
57
|
+
test "api contains production" do
|
58
|
+
@data["api"].delete("production")
|
59
|
+
assert_invalid
|
60
|
+
end
|
61
|
+
|
62
|
+
test "api contains production of https" do
|
63
|
+
@data["api"]["production"] = "http://foo.com"
|
64
|
+
assert_invalid
|
65
|
+
end
|
66
|
+
|
67
|
+
test "api contains config_vars array" do
|
68
|
+
@data["api"]["config_vars"] = "test"
|
69
|
+
assert_invalid
|
70
|
+
end
|
71
|
+
|
72
|
+
test "api contains at least one config var" do
|
73
|
+
@data["api"]["config_vars"].clear
|
74
|
+
assert_invalid
|
75
|
+
end
|
76
|
+
|
77
|
+
test "all config vars are in upper case" do
|
78
|
+
@data["api"]["config_vars"] << 'MYADDON_invalid_var'
|
79
|
+
assert_invalid
|
80
|
+
end
|
81
|
+
|
82
|
+
test "assert config var prefixes match addon id" do
|
83
|
+
@data["api"]["config_vars"] << 'MONGO_URL'
|
84
|
+
assert_invalid
|
85
|
+
end
|
86
|
+
|
87
|
+
test "plans key must exist" do
|
88
|
+
@data.delete("plans")
|
89
|
+
assert_invalid
|
90
|
+
end
|
91
|
+
|
92
|
+
test "plans key must be an Array" do
|
93
|
+
@data["plans"] = ""
|
94
|
+
assert_invalid
|
95
|
+
end
|
96
|
+
|
97
|
+
test "has at least one plan" do
|
98
|
+
@data["plans"] = []
|
99
|
+
assert_invalid
|
100
|
+
end
|
101
|
+
|
102
|
+
test "all plans are a hash" do
|
103
|
+
@data["plans"][0] = ""
|
104
|
+
assert_invalid
|
105
|
+
end
|
106
|
+
|
107
|
+
test "all plans have an id" do
|
108
|
+
@data["plans"].first.delete("id")
|
109
|
+
assert_invalid
|
110
|
+
end
|
111
|
+
|
112
|
+
test "all plans have an unique id" do
|
113
|
+
@data["plans"].first["id"] = @data["plans"].last["id"]
|
114
|
+
assert_invalid
|
115
|
+
end
|
116
|
+
|
117
|
+
test "all plans have a name" do
|
118
|
+
@data["plans"].first.delete("name")
|
119
|
+
assert_invalid
|
120
|
+
end
|
121
|
+
|
122
|
+
test "all plans have a unique name" do
|
123
|
+
@data["plans"].first["name"] = @data["plans"].last["name"]
|
124
|
+
assert_invalid
|
125
|
+
end
|
126
|
+
|
127
|
+
test "plans have a price" do
|
128
|
+
@data["plans"].first.delete("price")
|
129
|
+
assert_invalid
|
130
|
+
end
|
131
|
+
|
132
|
+
test "plans have an integer price" do
|
133
|
+
@data["plans"].first["price"] = "fiddy cent"
|
134
|
+
assert_invalid
|
135
|
+
end
|
136
|
+
|
137
|
+
test "plans have a valid price_unit" do
|
138
|
+
@data["plans"].first["price_unit"] = "first ov da munth"
|
139
|
+
assert_invalid
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/helper"
|
2
|
+
|
3
|
+
class ProvisionCheckTest < Test::Unit::TestCase
|
4
|
+
include Heroku::Sensei
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@data = Manifest.skeleton
|
8
|
+
@responses = [
|
9
|
+
[200, to_json({ :id => 456 })],
|
10
|
+
[401, "Unauthorized"]
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
def check ; ProvisionCheck ; end
|
15
|
+
|
16
|
+
test "valid on 200 for the regular check, and 401 for the auth check" do
|
17
|
+
assert_valid do |check|
|
18
|
+
stub :post, check, @responses
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
test "invalid JSON" do
|
23
|
+
@responses[0] = [200, "---"]
|
24
|
+
assert_invalid do |check|
|
25
|
+
stub :post, check, @responses
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
test "status other than 200" do
|
30
|
+
@responses[0] = [500, to_json({ :id => 456 })]
|
31
|
+
assert_invalid do |check|
|
32
|
+
stub :post, check, @responses
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
test "runs provision response check" do
|
37
|
+
@responses[0] = [200, to_json({ :noid => 456 })]
|
38
|
+
assert_invalid do |check|
|
39
|
+
stub :post, check, @responses
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
test "runs auth check" do
|
44
|
+
@responses[1] = [200, to_json({ :id => 456 })]
|
45
|
+
assert_invalid do |check|
|
46
|
+
stub :post, check, @responses
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/helper"
|
2
|
+
|
3
|
+
class ProvisionResponseCheckTest < Test::Unit::TestCase
|
4
|
+
include Heroku::Sensei
|
5
|
+
|
6
|
+
def check ; ProvisionResponseCheck ; end
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@response = { "id" => "123" }
|
10
|
+
@data = Manifest.skeleton.merge(:provision_response => @response)
|
11
|
+
@data['api']['config_vars'] << "MYADDON_CONFIG"
|
12
|
+
end
|
13
|
+
|
14
|
+
test "is valid if no errors" do
|
15
|
+
assert_valid
|
16
|
+
end
|
17
|
+
|
18
|
+
test "has an id" do
|
19
|
+
@response.delete("id")
|
20
|
+
assert_invalid
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "when config is present" do
|
24
|
+
|
25
|
+
test "is a hash" do
|
26
|
+
@response["config"] = ""
|
27
|
+
assert_invalid
|
28
|
+
end
|
29
|
+
|
30
|
+
test "each key is previously set in the manifest" do
|
31
|
+
@response["config"] = { "MYSQL_URL" => "http://..." }
|
32
|
+
assert_invalid
|
33
|
+
end
|
34
|
+
|
35
|
+
test "each value is a string" do
|
36
|
+
@response["config"] = { "MYADDON_URL" => {} }
|
37
|
+
assert_invalid
|
38
|
+
end
|
39
|
+
|
40
|
+
test "asserts _URL vars are valid URIs" do
|
41
|
+
@response["config"] = { "MYADDON_URL" => "abc:" }
|
42
|
+
assert_invalid
|
43
|
+
end
|
44
|
+
|
45
|
+
test "asserts _URL vars have a host" do
|
46
|
+
@response["config"] = { "MYADDON_URL" => "path" }
|
47
|
+
assert_invalid
|
48
|
+
end
|
49
|
+
|
50
|
+
test "asserts _URL vars have a scheme" do
|
51
|
+
@response["config"] = { "MYADDON_URL" => "//host/path" }
|
52
|
+
assert_invalid
|
53
|
+
end
|
54
|
+
|
55
|
+
test "doesn't run URI test against other vars" do
|
56
|
+
@response["config"] = { "MYADDON_CONFIG" => "abc:" }
|
57
|
+
assert_valid
|
58
|
+
end
|
59
|
+
|
60
|
+
test "doesn't allow localhost URIs on production" do
|
61
|
+
@data[:env] = 'production'
|
62
|
+
@response["config"] = { "MYADDON_URL" => "http://localhost/abc" }
|
63
|
+
assert_invalid
|
64
|
+
end
|
65
|
+
|
66
|
+
test "is valid otherwise" do
|
67
|
+
@response["config"] = { "MYADDON_URL" => "http://localhost/abc" }
|
68
|
+
assert_valid
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
|
4
|
+
enable :sessions
|
5
|
+
|
6
|
+
helpers do
|
7
|
+
def unauthorized!(status=403)
|
8
|
+
throw(:halt, [status, "Not authorized\n"])
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_token
|
12
|
+
Digest::SHA1.hexdigest([params[:id], 'SSO_SALT', params[:timestamp]].join(':'))
|
13
|
+
end
|
14
|
+
|
15
|
+
def login
|
16
|
+
session[:logged_in] = true
|
17
|
+
redirect '/'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
get '/working/heroku/resources/:id' do
|
22
|
+
unauthorized! unless params[:id] && params[:token]
|
23
|
+
unauthorized! unless params[:timestamp].to_i > (Time.now-60*2).to_i
|
24
|
+
unauthorized! unless params[:token] == make_token
|
25
|
+
login
|
26
|
+
end
|
27
|
+
|
28
|
+
get '/notoken/heroku/resources/:id' do
|
29
|
+
unauthorized! unless params[:id] && params[:token]
|
30
|
+
unauthorized! unless params[:timestamp].to_i > (Time.now-60*2).to_i
|
31
|
+
login
|
32
|
+
end
|
33
|
+
|
34
|
+
get '/notimestamp/heroku/resources/:id' do
|
35
|
+
unauthorized! unless params[:id] && params[:token]
|
36
|
+
unauthorized! unless params[:token] == make_token
|
37
|
+
login
|
38
|
+
end
|
39
|
+
|
40
|
+
get '/' do
|
41
|
+
unauthorized! unless session[:logged_in]
|
42
|
+
"OK"
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/helper"
|
2
|
+
|
3
|
+
class SsoCheckTest < Test::Unit::TestCase
|
4
|
+
include Heroku::Sensei
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@data = Manifest.skeleton.merge :id => 123
|
8
|
+
@data['api']['sso_salt'] = 'SSO_SALT'
|
9
|
+
end
|
10
|
+
|
11
|
+
def check ; SsoCheck ; end
|
12
|
+
|
13
|
+
test "working sso request" do
|
14
|
+
@data['api']['test'] += "working"
|
15
|
+
assert_valid
|
16
|
+
end
|
17
|
+
|
18
|
+
test "rejects bad token" do
|
19
|
+
@data['api']['test'] += "notoken"
|
20
|
+
assert_invalid
|
21
|
+
end
|
22
|
+
|
23
|
+
test "rejects old timestamp" do
|
24
|
+
@data['api']['test'] += "notimestamp"
|
25
|
+
assert_invalid
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|