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 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