bowtie-io 1.0.9 → 1.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bowtie.rb +7 -3
- data/lib/bowtie/commands/serve.rb +9 -0
- data/lib/bowtie/errors.rb +7 -0
- data/lib/bowtie/middleware/policy_check.rb +250 -0
- data/lib/bowtie/middleware/session.rb +17 -0
- data/lib/bowtie/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b07ca123fc9b68ead6a3dd5c5a918dbc622f517d
|
4
|
+
data.tar.gz: 5adb7f910c10e14cb2cf82180c9fc2b628ac480d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19237df1aefb5f8d1291b31939135db3f86828219b1d5f7831ff698b9e4db7ba12e8086589c1ab609fcc71f985653246dd9197bab4fd3f3a27f8239c90d17a48
|
7
|
+
data.tar.gz: 8e6940e20ff58f6b9610d6bc87dc60447e4fc86e89c672e4d8c386614a31ade8c13528787c70be2c8373338238a7696589b52826ebaa473c1cc558a1e8ca13ce
|
data/lib/bowtie.rb
CHANGED
@@ -3,12 +3,15 @@ require 'json'
|
|
3
3
|
require 'jekyll'
|
4
4
|
require 'rack'
|
5
5
|
require 'rack/streaming_proxy'
|
6
|
+
require 'restclient'
|
6
7
|
|
7
8
|
module Bowtie
|
8
9
|
module Middleware
|
9
|
-
autoload :Proxy,
|
10
|
-
autoload :Static,
|
11
|
-
autoload :Rewrite,
|
10
|
+
autoload :Proxy , 'bowtie/middleware/proxy'
|
11
|
+
autoload :Static , 'bowtie/middleware/static'
|
12
|
+
autoload :Rewrite , 'bowtie/middleware/rewrite'
|
13
|
+
autoload :PolicyCheck , 'bowtie/middleware/policy_check'
|
14
|
+
autoload :Session , 'bowtie/middleware/session'
|
12
15
|
end
|
13
16
|
|
14
17
|
autoload :Settings, 'bowtie/settings'
|
@@ -17,3 +20,4 @@ end
|
|
17
20
|
|
18
21
|
require 'bowtie/command'
|
19
22
|
require 'bowtie/commands/serve'
|
23
|
+
require 'bowtie/errors'
|
@@ -17,6 +17,8 @@ module Bowtie
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def process(options)
|
20
|
+
Bowtie::Middleware::PolicyCheck.watch!
|
21
|
+
|
20
22
|
Rack::Server.start(app: application(options),
|
21
23
|
Port: options['port'] || 4000,
|
22
24
|
Host: options['host'],
|
@@ -30,10 +32,14 @@ module Bowtie
|
|
30
32
|
Rack::Builder.new do
|
31
33
|
use Rack::CommonLogger
|
32
34
|
use Rack::ShowExceptions
|
35
|
+
use Rack::Session::Cookie, key: 'client.session',
|
36
|
+
secret: '_bowtie_client_local'
|
37
|
+
|
33
38
|
use Bowtie::Middleware::Rewrite, options
|
34
39
|
|
35
40
|
# User management provided by BowTie /users/*
|
36
41
|
map '/users' do
|
42
|
+
use Bowtie::Middleware::Session
|
37
43
|
use Bowtie::Middleware::Proxy
|
38
44
|
end
|
39
45
|
|
@@ -42,6 +48,9 @@ module Bowtie
|
|
42
48
|
use Bowtie::Middleware::Proxy
|
43
49
|
end
|
44
50
|
|
51
|
+
# Policy checks
|
52
|
+
use Bowtie::Middleware::PolicyCheck
|
53
|
+
|
45
54
|
# Static file server
|
46
55
|
use Bowtie::Middleware::Static, urls: [''],
|
47
56
|
root: options['destination'],
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# Checks .bowtie.yml policy definitions to permit or deny access
|
2
|
+
# to the requested resource.
|
3
|
+
|
4
|
+
require 'listen'
|
5
|
+
|
6
|
+
module Bowtie::Middleware
|
7
|
+
class PolicyCheck
|
8
|
+
def self.watch!
|
9
|
+
source = Jekyll.configuration['source']
|
10
|
+
|
11
|
+
# Initialize our policies with current source
|
12
|
+
Policy.load_policies!(source)
|
13
|
+
|
14
|
+
# Watch for policy file changes and reload policies on change
|
15
|
+
Listen.to(source, only: /\.bowtie\.yml/){
|
16
|
+
begin
|
17
|
+
Policy.load_policies!(source)
|
18
|
+
rescue => e
|
19
|
+
puts e.inspect
|
20
|
+
puts e.backtrace.join("\n")
|
21
|
+
end
|
22
|
+
}.start
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(app)
|
26
|
+
@app = app
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(env)
|
31
|
+
rack_request = Rack::Request.new(env)
|
32
|
+
status, headers, response = @app.call(env)
|
33
|
+
|
34
|
+
if Policy.permits?(rack_request)
|
35
|
+
[status, headers, response]
|
36
|
+
else
|
37
|
+
[403, {}, ["Request not permitted by defined policies"]]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
module Loader
|
43
|
+
extend self
|
44
|
+
|
45
|
+
CONFIG_FILE_NAME = '.bowtie.yml'
|
46
|
+
CONFIG_BLOCK_KEY = 'permits'
|
47
|
+
CONFIG_METHODS_KEY = 'method'
|
48
|
+
CONFIG_PLANS_KEY = 'plans'
|
49
|
+
CONFIG_PATH_KEY = 'path'
|
50
|
+
CONFIG_PROFILE_KEY = 'profile'
|
51
|
+
|
52
|
+
def branch; 'development' end
|
53
|
+
|
54
|
+
def policy_records(source)
|
55
|
+
records = Dir["#{source}/**/.bowtie.yml"].collect { |path|
|
56
|
+
policy_records_for_path(path.gsub(source, ''), File.read(path))
|
57
|
+
}.compact.flatten
|
58
|
+
|
59
|
+
records << default_permit_all if records.length == 0
|
60
|
+
|
61
|
+
records
|
62
|
+
end
|
63
|
+
|
64
|
+
def default_permit_all
|
65
|
+
Policy.new(branch,
|
66
|
+
'',
|
67
|
+
nil,
|
68
|
+
nil,
|
69
|
+
0,
|
70
|
+
nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
def policy_records_for_path(path, content)
|
74
|
+
base_path = path.gsub(CONFIG_FILE_NAME, '')
|
75
|
+
base_path = "/#{base_path}" unless base_path.start_with? '/'
|
76
|
+
base_path = base_path[0..-2] if base_path.end_with? '/'
|
77
|
+
|
78
|
+
branch_config = YAML.load(content)[branch] || {}
|
79
|
+
|
80
|
+
permitted_section_configs = branch_config[CONFIG_BLOCK_KEY] || []
|
81
|
+
permitted_section_configs = [permitted_section_configs] unless permitted_section_configs.is_a? Array
|
82
|
+
|
83
|
+
policy_records_from_permitted_section_configs(base_path, permitted_section_configs)
|
84
|
+
end
|
85
|
+
|
86
|
+
def policy_records_from_permitted_section_configs(base_path, configs)
|
87
|
+
records = []
|
88
|
+
|
89
|
+
configs.each do |config|
|
90
|
+
records += policy_records_from_permitted_section_config(base_path, config)
|
91
|
+
end
|
92
|
+
|
93
|
+
return records
|
94
|
+
end
|
95
|
+
|
96
|
+
def policy_records_from_permitted_section_config(base_path, config)
|
97
|
+
path_extension = config[CONFIG_PATH_KEY]
|
98
|
+
path_extension = path_extension[1..-1] if path_extension && path_extension.start_with?('/')
|
99
|
+
|
100
|
+
policy_path = [base_path, path_extension].compact.join('/')
|
101
|
+
|
102
|
+
methods = methods_from_permitted_section_config(config)
|
103
|
+
plans = plans_from_permitted_section_config(config)
|
104
|
+
profile_restrictions = profile_restrictions_from_permitted_section_config(config)
|
105
|
+
|
106
|
+
records = []
|
107
|
+
|
108
|
+
methods.each do |method|
|
109
|
+
plans.each do |plan|
|
110
|
+
records << Policy.new(branch,
|
111
|
+
policy_path,
|
112
|
+
method,
|
113
|
+
plan,
|
114
|
+
policy_path.length,
|
115
|
+
profile_restrictions)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
return records
|
120
|
+
end
|
121
|
+
|
122
|
+
def methods_from_permitted_section_config(config)
|
123
|
+
method_config = config[CONFIG_METHODS_KEY]
|
124
|
+
|
125
|
+
if method_config.nil? || method_config == '*'
|
126
|
+
[nil]
|
127
|
+
else
|
128
|
+
if method_config.is_a? Array
|
129
|
+
method_config.map(&:upcase)
|
130
|
+
elsif method_config.is_a? String
|
131
|
+
[method_config].map(&:upcase)
|
132
|
+
else
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def plans_from_permitted_section_config(config)
|
139
|
+
plan_config = config[CONFIG_PLANS_KEY]
|
140
|
+
|
141
|
+
if plan_config.nil? || plan_config == '*'
|
142
|
+
[nil]
|
143
|
+
else
|
144
|
+
if plan_config.is_a? Array
|
145
|
+
plan_config
|
146
|
+
else
|
147
|
+
[plan_config]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def profile_restrictions_from_permitted_section_config(config)
|
153
|
+
_profile_restrictions_config = config[CONFIG_PROFILE_KEY]
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
Policy = Struct.new(:branch,
|
159
|
+
:path,
|
160
|
+
:request_method,
|
161
|
+
:plan,
|
162
|
+
:weight,
|
163
|
+
:profile_restrictions) do
|
164
|
+
class << self
|
165
|
+
attr_reader :all
|
166
|
+
|
167
|
+
def load_policies!(source)
|
168
|
+
@all = Loader.policy_records(source)
|
169
|
+
end
|
170
|
+
|
171
|
+
def permits?(rack_request)
|
172
|
+
policies = applicable_for(rack_request)
|
173
|
+
|
174
|
+
# When there's no applicable policy, the request is not permitted
|
175
|
+
return false if policies.length == 0
|
176
|
+
|
177
|
+
!!policies.detect { |policy|
|
178
|
+
policy.permits?(rack_request)
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def applicable_for(rack_request)
|
183
|
+
applicable_to_request = all.select { |policy|
|
184
|
+
policy.branch == 'development' &&
|
185
|
+
rack_request.path.start_with?(policy.path)
|
186
|
+
}.sort_by!(&:weight).reverse
|
187
|
+
|
188
|
+
heaviest = applicable_to_request.first
|
189
|
+
|
190
|
+
applicable_to_request.select { |policy|
|
191
|
+
policy.weight >= heaviest.weight
|
192
|
+
}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def permits?(rack_request)
|
197
|
+
request_method_permitted?(rack_request) &&
|
198
|
+
plan_permitted?(rack_request) &&
|
199
|
+
profile_permitted?(rack_request)
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
def request_method_permitted?(rack_request)
|
204
|
+
request_method.nil? ||
|
205
|
+
request_method == rack_request.request_method
|
206
|
+
end
|
207
|
+
|
208
|
+
def plan_permitted?(rack_request)
|
209
|
+
plan.nil? ||
|
210
|
+
(rack_request.env['rack.session']['user']['stripe_plan_id'] rescue nil) == plan
|
211
|
+
end
|
212
|
+
|
213
|
+
def profile_permitted?(rack_request)
|
214
|
+
if profile_restrictions.nil?
|
215
|
+
true
|
216
|
+
else
|
217
|
+
profile_restrictions.each do |scope,values|
|
218
|
+
profile = user_profile(rack_request, scope)
|
219
|
+
return false if profile.nil?
|
220
|
+
|
221
|
+
values.each do |key, value|
|
222
|
+
return false if profile[key] != value
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
return true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def user_profile(rack_request, scope)
|
231
|
+
user_id = rack_request.env['rack.session']['user']['id'] rescue nil
|
232
|
+
return nil if user_id.nil?
|
233
|
+
|
234
|
+
response = RestClient.get(user_profile_endpoint_url(user_id, scope))
|
235
|
+
JSON.parse(response.body)
|
236
|
+
end
|
237
|
+
|
238
|
+
def user_profile_endpoint_url(user_id, scope)
|
239
|
+
secret_key = Bowtie::Settings['project']['secret_key']
|
240
|
+
raise SecretKeyMissingError.new if secret_key.nil?
|
241
|
+
|
242
|
+
URI::HTTPS.build(host: Bowtie::Settings['project']['fqdn']['development'],
|
243
|
+
userinfo: "#{Bowtie::Settings['project']['secret_key']}:",
|
244
|
+
path: "/bowtie/api/users/#{user_id}/profile.json",
|
245
|
+
query: "scope=#{scope}").to_s
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Bowtie::Middleware
|
2
|
+
class Session
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
status, headers, response = @app.call(env)
|
9
|
+
|
10
|
+
if headers['X-Bowtie-Client-Session']
|
11
|
+
env['rack.session']['user'] = JSON.parse(headers['X-Bowtie-Client-Session'])
|
12
|
+
end
|
13
|
+
|
14
|
+
[status, headers, response]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/bowtie/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bowtie-io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Kassemi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rest-client
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.8.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.8.0
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: sass
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,8 +92,11 @@ files:
|
|
78
92
|
- lib/bowtie.rb
|
79
93
|
- lib/bowtie/command.rb
|
80
94
|
- lib/bowtie/commands/serve.rb
|
95
|
+
- lib/bowtie/errors.rb
|
96
|
+
- lib/bowtie/middleware/policy_check.rb
|
81
97
|
- lib/bowtie/middleware/proxy.rb
|
82
98
|
- lib/bowtie/middleware/rewrite.rb
|
99
|
+
- lib/bowtie/middleware/session.rb
|
83
100
|
- lib/bowtie/middleware/static.rb
|
84
101
|
- lib/bowtie/settings.rb
|
85
102
|
- lib/bowtie/version.rb
|