bowtie-io 1.0.9 → 1.0.10
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.
- 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
|