locomotivecms_steam 1.5.0.beta3 → 1.5.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -4
  3. data/Gemfile.lock +3 -6
  4. data/README.md +33 -0
  5. data/Rakefile +8 -3
  6. data/docker-compose.yml +15 -0
  7. data/lib/locomotive/steam/entities/content_type.rb +7 -2
  8. data/lib/locomotive/steam/liquid/drops/section_content_proxy.rb +4 -0
  9. data/lib/locomotive/steam/liquid/filters/translate.rb +2 -0
  10. data/lib/locomotive/steam/liquid/tags/link_to.rb +8 -1
  11. data/lib/locomotive/steam/liquid/tags/model_form.rb +7 -1
  12. data/lib/locomotive/steam/middlewares/auth.rb +10 -17
  13. data/lib/locomotive/steam/middlewares/concerns/auth_helpers.rb +30 -0
  14. data/lib/locomotive/steam/middlewares/concerns/helpers.rb +13 -3
  15. data/lib/locomotive/steam/middlewares/concerns/liquid_context.rb +1 -1
  16. data/lib/locomotive/steam/middlewares/concerns/recaptcha.rb +30 -0
  17. data/lib/locomotive/steam/middlewares/default_env.rb +5 -3
  18. data/lib/locomotive/steam/middlewares/entry_submission.rb +4 -1
  19. data/lib/locomotive/steam/middlewares/impersonated_entry.rb +73 -0
  20. data/lib/locomotive/steam/middlewares/locale.rb +14 -9
  21. data/lib/locomotive/steam/models/associations/belongs_to.rb +6 -0
  22. data/lib/locomotive/steam/server.rb +1 -0
  23. data/lib/locomotive/steam/services.rb +9 -1
  24. data/lib/locomotive/steam/services/action_service.rb +3 -3
  25. data/lib/locomotive/steam/services/auth_service.rb +1 -1
  26. data/lib/locomotive/steam/services/content_entry_service.rb +11 -1
  27. data/lib/locomotive/steam/services/cookie_service.rb +25 -0
  28. data/lib/locomotive/steam/services/page_finder_service.rb +12 -0
  29. data/lib/locomotive/steam/services/recaptcha_service.rb +33 -0
  30. data/lib/locomotive/steam/services/translator_service.rb +12 -13
  31. data/lib/locomotive/steam/version.rb +1 -1
  32. data/spec/integration/liquid/filters/translate_spec.rb +7 -0
  33. data/spec/integration/repositories/content_entry_repository_spec.rb +4 -0
  34. data/spec/integration/server/auth_spec.rb +8 -1
  35. data/spec/integration/services/content_entry_service_spec.rb +11 -0
  36. data/spec/support/helpers.rb +1 -1
  37. data/spec/unit/liquid/drops/section_content_proxy_spec.rb +8 -0
  38. data/spec/unit/liquid/tags/action_spec.rb +2 -1
  39. data/spec/unit/liquid/tags/global_section_spec.rb +2 -1
  40. data/spec/unit/liquid/tags/link_to_spec.rb +7 -0
  41. data/spec/unit/liquid/tags/model_form_spec.rb +7 -0
  42. data/spec/unit/liquid/tags/section_spec.rb +2 -1
  43. data/spec/unit/liquid/tags/snippet_spec.rb +2 -1
  44. data/spec/unit/middlewares/auth_spec.rb +1 -0
  45. data/spec/unit/middlewares/entry_submission_spec.rb +64 -20
  46. data/spec/unit/middlewares/helpers_spec.rb +5 -1
  47. data/spec/unit/middlewares/impersonated_entry_spec.rb +80 -0
  48. data/spec/unit/middlewares/locale_spec.rb +57 -36
  49. data/spec/unit/middlewares/section_spec.rb +6 -5
  50. data/spec/unit/middlewares/site_spec.rb +13 -0
  51. data/spec/unit/services/action_service_spec.rb +17 -11
  52. data/spec/unit/services/auth_service_spec.rb +7 -1
  53. data/spec/unit/services/content_entry_service_spec.rb +25 -4
  54. data/spec/unit/services/cookie_service_spec.rb +38 -0
  55. data/spec/unit/services/page_finder_service_spec.rb +69 -0
  56. data/spec/unit/services/recaptcha_service_spec.rb +71 -0
  57. data/spec/unit/services/translator_service_spec.rb +11 -1
  58. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd4a12f87c87fc5603766c1b2358615b820c4c74a1de3808afc1446289883337
4
- data.tar.gz: e1c94bb3d492bd893409cd491fb74093666958286544a6f7bea749d5786ae508
3
+ metadata.gz: 2811bbc4085bab4ef718204d051285c1922ec6208d8173c0f2f7d21996eed632
4
+ data.tar.gz: dc8b0c4d1b7ec7a85006511f8aad93647e4fa7e1a12dda2fde0dc20f1cabb9b5
5
5
  SHA512:
6
- metadata.gz: 4413660fd7eb9284d6c92b88830864e7507d8757f1edc198628131c889d48c5373c68e54749b6c15b1964d19536215d7e7247344bd8784cd1aae7c48f48f5afc
7
- data.tar.gz: 2ef38c06b49efc2a5e3923db8935c5483618baf62f0f38699139aa339b29dbc05995eecbb7333ccca32ae20f1954dd9d535ee14cffab5fa0907516d2a3987295
6
+ metadata.gz: a4b744dfee082181c392bb21c56ff4f9a9d761be3066405ff17bc3007a25f3a519dc266bc409d39eb55ee6ea76ab1525b35daacdd17ef900d13637351a17c6b9
7
+ data.tar.gz: fc28966e95e7e9809aa7e3f153d7343d610659602d39af2c92364bd2213be064ed4b6d270b07b1eefd4c5e8fa3150e57821c5db2b58c92147708c4f911ea7b06
data/Gemfile CHANGED
@@ -35,7 +35,3 @@ group :test do
35
35
  gem 'codeclimate-test-reporter', '~> 0.4.7', require: false
36
36
  gem 'coveralls', '~> 0.8.1', require: false
37
37
  end
38
-
39
- platform :ruby do
40
- ruby '2.5.0'
41
- end
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- locomotivecms_steam (1.5.0.beta3)
15
+ locomotivecms_steam (1.5.0.rc0)
16
16
  RedCloth (~> 4.3.2)
17
17
  autoprefixer-rails (~> 8.0.0)
18
18
  bcrypt (~> 3.1.11)
@@ -120,7 +120,7 @@ GEM
120
120
  memory_profiler (0.9.12)
121
121
  mime-types (3.1)
122
122
  mime-types-data (~> 3.2015)
123
- mime-types-data (3.2018.0812)
123
+ mime-types-data (3.2019.0331)
124
124
  mimetype-fu (0.1.2)
125
125
  mini_mime (1.0.1)
126
126
  mini_portile2 (2.3.0)
@@ -136,7 +136,7 @@ GEM
136
136
  nokogumbo (1.5.0)
137
137
  nokogiri
138
138
  origin (2.3.1)
139
- pony (1.13)
139
+ pony (1.13.1)
140
140
  mail (>= 2.0)
141
141
  public_suffix (3.0.3)
142
142
  puma (3.12.0)
@@ -218,8 +218,5 @@ DEPENDENCIES
218
218
  stackprof
219
219
  timecop (~> 0.9.1)
220
220
 
221
- RUBY VERSION
222
- ruby 2.5.0p0
223
-
224
221
  BUNDLED WITH
225
222
  1.17.3
data/README.md CHANGED
@@ -59,6 +59,39 @@ see the list in the issues section.
59
59
  4. Push to the branch (`git push origin my-new-feature`)
60
60
  5. Create new Pull Request
61
61
 
62
+ ## Running test with docker
63
+
64
+ A docker configuration is included in this repository, you can run all the tests with the following procedure
65
+
66
+ First run a bash shell inside the container
67
+
68
+ ```
69
+ docker-compose run steam gosu ubuntu bash
70
+ ```
71
+
72
+ Now you are inside the container you can
73
+
74
+ Init the database with demo data
75
+
76
+ ```
77
+ rake mongodb:test:seed
78
+ ```
79
+
80
+ Run all the test with
81
+
82
+ ```
83
+ rake spec
84
+ ```
85
+
86
+ Run specific test with rspec
87
+
88
+ ```
89
+ rspec spec/unit/middlewares/locale_spec.rb
90
+
91
+ ```
92
+
93
+ Note: you do not need to prefix with bundle exec as the docky-ruby image already add it automatically
94
+
62
95
  ## License
63
96
 
64
97
  Copyright (c) 2018 NoCoffee. MIT Licensed, see MIT-LICENSE for details.
data/Rakefile CHANGED
@@ -13,17 +13,22 @@ namespace :mongodb do
13
13
  task :seed do
14
14
  root_path = File.expand_path(File.dirname(__FILE__))
15
15
  db_path = File.join(root_path, 'spec', 'fixtures', 'mongodb')
16
+ if ENV['MONGO_HOST']
17
+ host = "--host=#{ENV['MONGO_HOST']}"
18
+ else
19
+ host = ""
20
+ end
16
21
 
17
22
  if database = ENV['DATABASE']
18
23
  dump_path = File.join(root_path, 'dump', database)
19
24
 
20
25
  `rm -rf #{db_path}`
21
- `mongodump --db #{database}`
26
+ `mongodump --db #{database} #{host}`
22
27
  `mv #{dump_path} #{db_path}`
23
28
  end
24
29
 
25
- `mongo steam_test_1_5_x --eval "db.dropDatabase()"`
26
- `mongorestore -d steam_test_1_5_x #{db_path}`
30
+ `mongo steam_test_1_5_x --eval "db.dropDatabase()" #{host}`
31
+ `mongorestore -d steam_test_1_5_x #{db_path} #{host}`
27
32
 
28
33
  puts "Done! Update now the spec/support/helpers.rb file by setting the new id of the site returned by the mongodb_site_id method"
29
34
  end
@@ -0,0 +1,15 @@
1
+ services:
2
+ db:
3
+ image: mongo:3
4
+ volumes:
5
+ - ./data/db:/data/db
6
+ steam:
7
+ image: quay.io/akretion/docky-ruby:latest
8
+ environment:
9
+ - MONGO_HOST=db
10
+ volumes:
11
+ - .:/workspace
12
+ - ./bundle:/usr/local/bundle
13
+ depends_on:
14
+ - db
15
+ version: '3'
@@ -13,8 +13,9 @@ module Locomotive::Steam
13
13
 
14
14
  def initialize(attributes = {})
15
15
  super({
16
- order_by: '_position',
17
- order_direction: 'asc'
16
+ order_by: '_position',
17
+ order_direction: 'asc',
18
+ recaptcha_required: false
18
19
  }.merge(attributes))
19
20
  end
20
21
 
@@ -68,5 +69,9 @@ module Locomotive::Steam
68
69
  { name.to_sym => self.order_direction.to_s }
69
70
  end
70
71
 
72
+ def recaptcha_required?
73
+ !!self.recaptcha_required
74
+ end
75
+
71
76
  end
72
77
  end
@@ -85,6 +85,10 @@ module Locomotive
85
85
  @new_window
86
86
  end
87
87
 
88
+ def new_window_attribute
89
+ !!@new_window ? 'target="_blank"' : ''
90
+ end
91
+
88
92
  def to_s
89
93
  @url
90
94
  end
@@ -15,6 +15,8 @@ module Locomotive
15
15
  @context.registers[:services].translator.translate(input, options) || input
16
16
  end
17
17
 
18
+ alias t translate
19
+
18
20
  end
19
21
 
20
22
  ::Liquid::Template.register_filter(Translate)
@@ -18,7 +18,10 @@ module Locomotive
18
18
  end
19
19
  end
20
20
 
21
- %{<a href="#{path}">#{label}</a>}
21
+ tag_href = %(href="#{path}")
22
+ tag_class = %( class="#{css}") if css.present?
23
+
24
+ %{<a #{tag_href}#{tag_class}>#{label}</a>}
22
25
  end
23
26
  end
24
27
 
@@ -36,6 +39,10 @@ module Locomotive
36
39
  end
37
40
  end
38
41
 
42
+ def css
43
+ @path_options[:class]
44
+ end
45
+
39
46
  end
40
47
 
41
48
  ::Liquid::Template.register_tag('link_to', LinkTo)
@@ -27,7 +27,7 @@ module Locomotive
27
27
  form_attributes = prepare_form_attributes(options)
28
28
 
29
29
  html_content_tag :form,
30
- content_type_html(name) + csrf_html + callbacks_html(options) + yield,
30
+ content_type_html(name) + csrf_html + callbacks_html(options) + recaptcha_html(options) + yield,
31
31
  form_attributes
32
32
  end
33
33
 
@@ -47,6 +47,12 @@ module Locomotive
47
47
  end.join('')
48
48
  end
49
49
 
50
+ def recaptcha_html(options)
51
+ return '' if options[:recaptcha] != true
52
+
53
+ html_tag :input, type: 'hidden', name: 'g-recaptcha-response', id: 'g-recaptcha-response'
54
+ end
55
+
50
56
  private
51
57
 
52
58
  def html_content_tag(name, content, options = {})
@@ -14,6 +14,8 @@ module Locomotive::Steam
14
14
  class Auth < ThreadSafe
15
15
 
16
16
  include Concerns::Helpers
17
+ include Concerns::AuthHelpers
18
+ include Concerns::Recaptcha
17
19
 
18
20
  def _call
19
21
  load_authenticated_entry
@@ -28,7 +30,7 @@ module Locomotive::Steam
28
30
  private
29
31
 
30
32
  def sign_up(options)
31
- return if authenticated?
33
+ return if authenticated? || !is_recaptcha_valid?(options.type, options.recaptcha_response)
32
34
 
33
35
  status, entry = services.auth.sign_up(options, default_liquid_context, request)
34
36
 
@@ -62,6 +64,8 @@ module Locomotive::Steam
62
64
 
63
65
  store_authenticated(nil)
64
66
 
67
+ redirect_to options.callback || path
68
+
65
69
  append_message(:signed_out)
66
70
  end
67
71
 
@@ -91,26 +95,11 @@ module Locomotive::Steam
91
95
  entry_id = request.session[:authenticated_entry_id]
92
96
 
93
97
  if entry = services.auth.find_authenticated_resource(entry_type, entry_id)
94
- env['authenticated_entry'] = entry
98
+ env['steam.authenticated_entry'] = entry
95
99
  liquid_assigns["current_#{entry_type.singularize}"] = entry
96
100
  end
97
101
  end
98
102
 
99
- def authenticated?
100
- !!env['authenticated_entry']
101
- end
102
-
103
- def store_authenticated(entry)
104
- type = entry ? entry.content_type.slug : request.session[:authenticated_entry_type]
105
-
106
- request.session[:authenticated_entry_type] = type.to_s
107
- request.session[:authenticated_entry_id] = entry.try(:_id).to_s
108
-
109
- log "[Auth] authenticated #{type.to_s.singularize} ##{entry.try(:_id).to_s}"
110
-
111
- liquid_assigns["current_#{type.singularize}"] = entry
112
- end
113
-
114
103
  def append_message(message)
115
104
  log "[Auth] status message = #{message.inspect}"
116
105
 
@@ -192,6 +181,10 @@ module Locomotive::Steam
192
181
  @config ||= _read_smtp_config
193
182
  end
194
183
 
184
+ def recaptcha_response
185
+ params["g-recaptcha-response"]
186
+ end
187
+
195
188
  def smtp
196
189
  if smtp_config.blank?
197
190
  {}
@@ -0,0 +1,30 @@
1
+ module Locomotive::Steam
2
+ module Middlewares
3
+ module Concerns
4
+ module AuthHelpers
5
+
6
+ def authenticated?
7
+ !!env['steam.authenticated_entry']
8
+ end
9
+
10
+ def authenticated_entry_type
11
+ request.session[:authenticated_entry_type]
12
+ end
13
+
14
+ def store_authenticated(entry)
15
+ type = entry ? entry.content_type.slug : authenticated_entry_type
16
+
17
+ request.session[:authenticated_entry_type] = type.to_s
18
+ request.session[:authenticated_entry_id] = entry&._id.to_s
19
+
20
+ env['steam.authenticated_entry'] = nil if entry.nil?
21
+
22
+ log "[Auth] authenticated #{type.to_s.singularize} ##{entry&._id.to_s}"
23
+
24
+ liquid_assigns["current_#{type.singularize}"] = entry
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -4,7 +4,7 @@ module Locomotive::Steam
4
4
  module Helpers
5
5
 
6
6
  def html?
7
- ['text/html', 'application/x-www-form-urlencoded', 'multipart/form-data'].include?(self.request.media_type) &&
7
+ [nil, 'text/html', 'application/x-www-form-urlencoded', 'multipart/form-data'].include?(self.request.media_type) &&
8
8
  !self.request.xhr? &&
9
9
  !self.json?
10
10
  end
@@ -15,7 +15,7 @@ module Locomotive::Steam
15
15
 
16
16
  def render_response(content, code = 200, type = nil)
17
17
  _headers = env['steam.headers'] || {}
18
-
18
+ inject_cookies(_headers)
19
19
  @next_response = [
20
20
  code,
21
21
  { 'Content-Type' => type || 'text/html' }.merge(_headers),
@@ -28,7 +28,17 @@ module Locomotive::Steam
28
28
 
29
29
  self.log "Redirected to #{_location}".blue
30
30
 
31
- @next_response = [type, { 'Content-Type' => 'text/html', 'Location' => _location }, []]
31
+ headers = { 'Content-Type' => 'text/html', 'Location' => _location }
32
+ inject_cookies(headers)
33
+
34
+ @next_response = [type, headers, []]
35
+ end
36
+
37
+ def inject_cookies(headers)
38
+ _cookies = env['steam.cookies'] || {}
39
+ _cookies.each do |key, vals|
40
+ Rack::Utils.set_cookie_header!(headers, key, vals.symbolize_keys!)
41
+ end
32
42
  end
33
43
 
34
44
  def modify_path(path = nil, &block)
@@ -22,7 +22,7 @@ module Locomotive::Steam
22
22
  params: params,
23
23
  session: request.session,
24
24
  cookies: request.cookies
25
- }
25
+ }.merge(env['steam.liquid_registers'])
26
26
  end
27
27
 
28
28
  def liquid_assigns
@@ -0,0 +1,30 @@
1
+ module Locomotive::Steam
2
+ module Middlewares
3
+ module Concerns
4
+ module Recaptcha
5
+
6
+ def is_recaptcha_valid?(slug, response_code)
7
+ !is_recaptcha_required?(slug) || is_recaptcha_verified?(response_code)
8
+ end
9
+
10
+ def is_recaptcha_required?(slug)
11
+ type = services.content_entry.get_type(slug)
12
+ type&.recaptcha_required?
13
+ end
14
+
15
+ def is_recaptcha_verified?(response_code)
16
+ services.recaptcha.verify(response_code).tap do |valid|
17
+ liquid_assigns['recaptcha_invalid'] = !valid
18
+ end
19
+ end
20
+
21
+ def build_invalid_recaptcha_entry(slug, entry_attributes)
22
+ services.content_entry.build(slug, entry_attributes).tap do |entry|
23
+ entry.errors.add(:recaptcha_invalid, true)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -8,9 +8,11 @@ module Locomotive::Steam
8
8
  def call(env)
9
9
  request = Rack::Request.new(env)
10
10
 
11
- env['steam.request'] = request
12
- env['steam.services'] = build_services(request)
13
- env['steam.liquid_assigns'] = {}
11
+ env['steam.request'] = request
12
+ env['steam.services'] = build_services(request)
13
+ env['steam.liquid_registers'] = {}
14
+ env['steam.liquid_assigns'] = {}
15
+ env['steam.cookies'] = {}
14
16
 
15
17
  app.call(env)
16
18
  end
@@ -6,6 +6,7 @@ module Locomotive::Steam
6
6
  class EntrySubmission < ThreadSafe
7
7
 
8
8
  include Concerns::Helpers
9
+ include Concerns::Recaptcha
9
10
 
10
11
  HTTP_REGEXP = /^https?:\/\//o
11
12
  ENTRY_SUBMISSION_REGEXP = /^\/entry_submissions\/(\w+)/o
@@ -141,7 +142,9 @@ module Locomotive::Steam
141
142
  #
142
143
  #
143
144
  def create_entry(slug)
144
- if entry = entry_submission.submit(slug, entry_attributes)
145
+ if !is_recaptcha_valid?(slug, params[:'g-recaptcha-response'])
146
+ build_invalid_recaptcha_entry(slug, entry_attributes)
147
+ elsif entry = entry_submission.submit(slug, entry_attributes)
145
148
  entry
146
149
  else
147
150
  raise %{Unknown content type "#{slug}" or public_submission_enabled property not true}
@@ -0,0 +1,73 @@
1
+ module Locomotive::Steam
2
+ module Middlewares
3
+
4
+ # If an account defined by a content type was impersonated from the back-office,
5
+ # load the entry and sign her/him in automatically.
6
+ #
7
+ # Besides, display a small banner at the top of the page in order to let
8
+ # the administrator stop the impersonation of the account.
9
+ #
10
+ class ImpersonatedEntry < ThreadSafe
11
+
12
+ include Concerns::Helpers
13
+ include Concerns::AuthHelpers
14
+
15
+ def _call
16
+ if params[:impersonating] == 'stop'
17
+ # sign out the current user
18
+ store_authenticated(nil)
19
+
20
+ # leave the impersonating mode
21
+ request.session[:authenticated_impersonation] = '0'
22
+
23
+ # redirect the user to the same page
24
+ redirect_to path, 302
25
+ elsif authenticated? && is_impersonating?
26
+ # useful if other middlewares need this information
27
+ env['steam.impersonating_authenticated_entry'] = true
28
+
29
+ # again, useful if the developer needs to add a "impersonate" button
30
+ # directly in her/his website in any Liquid template
31
+ liquid_assigns["is_impersonating_current_#{authenticated_entry_type.singularize}"] = true
32
+ end
33
+ end
34
+
35
+ def next
36
+ response = super
37
+
38
+ if authenticated? && is_impersonating? && html?
39
+ # get the authenticated entry from the Auth middleware
40
+ entry = env['steam.authenticated_entry']
41
+
42
+ # modify the HTML body by adding a banner at the bottom of the page
43
+ status, headers, body = response
44
+ [status, headers, [body.first.gsub(/(<body[^>]*>)/, '\1' + banner_html(entry))]]
45
+ else
46
+ response
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def is_impersonating?
53
+ request.session[:authenticated_impersonation] == '1'
54
+ end
55
+
56
+ def banner_html(entry)
57
+ <<-HTML
58
+ <div
59
+ class="locomotive-impersonating-banner"
60
+ style="position: fixed; bottom: 0; left: 0; z-index: 9999; width: 100%;background: #61D5AB; text-align: center; color: #fff; padding: 1rem 0rem;"
61
+ >
62
+ You're impersonating <b>#{entry._label}</b>.
63
+ <a href="?impersonating=stop" style="color: #fff; text-decoration: underline">
64
+ Stop impersonating
65
+ </a>
66
+ </div>
67
+ HTML
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+ end