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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2987e1107e9d726edcbb60495a4be2befad6bf18
4
- data.tar.gz: 34e884d22998a46cce60242c80fb098b59194aae
3
+ metadata.gz: b07ca123fc9b68ead6a3dd5c5a918dbc622f517d
4
+ data.tar.gz: 5adb7f910c10e14cb2cf82180c9fc2b628ac480d
5
5
  SHA512:
6
- metadata.gz: a56993e3f15c799905feaee1436abbbaf3785df79f8259e3f48cb4d4551730532e7da1cd6abef3bb1816150742c6df019aae305cb69c0119c682b7329a139aef
7
- data.tar.gz: 4b6d57854d16a7c5af4b44bcb1d65068164b6a8aa69be03c39df1634a5442913fbb43bbc31db8345a28ebaab4fc5a9132d01728fb95ecab64a32b2078d5a449e
6
+ metadata.gz: 19237df1aefb5f8d1291b31939135db3f86828219b1d5f7831ff698b9e4db7ba12e8086589c1ab609fcc71f985653246dd9197bab4fd3f3a27f8239c90d17a48
7
+ data.tar.gz: 8e6940e20ff58f6b9610d6bc87dc60447e4fc86e89c672e4d8c386614a31ade8c13528787c70be2c8373338238a7696589b52826ebaa473c1cc558a1e8ca13ce
@@ -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, 'bowtie/middleware/proxy'
10
- autoload :Static, 'bowtie/middleware/static'
11
- autoload :Rewrite, 'bowtie/middleware/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,7 @@
1
+ module Bowtie
2
+ class ProjectSecretKeyMissing < RuntimeError
3
+ def initialize(msg='You need "project": { "secret_key": "XYZ" } defined in `settings.json`')
4
+ super
5
+ end
6
+ end
7
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Bowtie
2
- VERSION = '1.0.9'
2
+ VERSION = '1.0.10'
3
3
  end
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.9
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-24 00:00:00.000000000 Z
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