site-wide password to prevent access to public visitors (WIP)

did committed Dec 12, 2015
commit f11814344d325e0f71d460a3bb23da704f7a75bd
Showing 5 changed files with 169 additions and 2 deletions
locomotive/steam/entities/site.rb b/lib/locomotive/steam/entities/site.rb +3 -1
@@ @@ -13,7 +13,9 @@ module Locomotive::Steam
template_version: nil,
domains: [],
redirect_to_first_domain: false,
- url_redirections: []
+ url_redirections: [],
+ private_access: false,
+ password: nil
}.merge(attributes))
end
locomotive/steam/middlewares/private_access.rb b/lib/locomotive/steam/middlewares/private_access.rb +70 -0
@@ @@ -0,0 +1,70 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # Hide a site behind a password to prevent public access.
+ # If page with the "lock_screen" handle exists, then it
+ # will be used to display the login form. Otherwise, a very basic
+ # form will be displayed.
+ #
+ class PrivateAccess < ThreadSafe
+
+ include Helpers
+
+ def _call
+ return if env['steam.private_access_disabled']
+
+ if site.private_access
+ log "Site with private access"
+
+ if access_granted?
+ store_password
+ else
+ render_response(lock_screen_html, 403)
+ end
+ end
+ end
+
+ private
+
+ def access_granted?
+ !submitted_password.blank? && submitted_password == site.password
+ end
+
+ def submitted_password
+ request.session[:private_access_password] || params[:private_access_password]
+ end
+
+ def store_password
+ request.session[:private_access_password] = params[:private_access_password] if params[:private_access_password].present?
+ end
+
+ def lock_screen_html
+ <<-HTML
+ <html>
+ <title>#{site.name} - Password protected</title>
+ <style>
+ @import url(http://fonts.googleapis.com/css?family=Open+Sans:400,700);
+ body { background: #f8f8f8; font-family: "Open Sans", sans-serif; font-size: 12px; }
+ form { position: relative; top: 50%; width: 300px; margin: 0px auto; transform: translateY(-50%); -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); }
+ form p { text-align: center; color: #d9684c; }
+ form input[type=password] { border: 2px solid #eee; font-size: 14px; padding: 5px 8px; background: #fff; }
+ form input[type=submit] { border: 0 none; padding: 6px 20px; background: #171717; color: #fff; font-size: 14px; text-transform: none; transition: all 100ms ease-in-out; cursor: pointer; }
+ form input[type=submit]:hover { opacity: .7; }
+ }
+ </style>
+ <body>
+ <form action="/#{mounted_on}" method="POST">
+ #{'<p>Wrong password</p>' unless submitted_password.blank?}
+ <input type="password" name="private_access_password" placeholder="Password" />
+ &nbsp;
+ <input type="submit" value="Unlock" />
+ </form>
+ </body>
+ </html>
+ HTML
+ end
+
+ end
+
+ end
+ end
locomotive/steam/middlewares/renderer.rb b/lib/locomotive/steam/middlewares/renderer.rb +2 -1
@@ @@ -95,7 +95,8 @@ module Locomotive::Steam
'url' => request.url,
'ip_address' => request.ip,
'post?' => request.post?,
- 'host' => request.host_with_port
+ 'host' => request.host_with_port,
+ 'mounted_on' => mounted_on
}
end
locomotive/steam/server.rb b/lib/locomotive/steam/server.rb +1 -0
@@ @@ -59,6 +59,7 @@ module Locomotive::Steam
Middlewares::UrlRedirection,
Middlewares::Robots,
Middlewares::Timezone,
+ Middlewares::PrivateAccess,
Middlewares::EntrySubmission,
Middlewares::Locale,
Middlewares::LocaleRedirection,
spec/unit/middlewares/private_access_spec.rb +93 -0
@@ @@ -0,0 +1,93 @@
+ require 'spec_helper'
+
+ require_relative '../../../lib/locomotive/steam/middlewares/thread_safe'
+ require_relative '../../../lib/locomotive/steam/middlewares/helpers'
+ require_relative '../../../lib/locomotive/steam/middlewares/private_access'
+
+ describe Locomotive::Steam::Middlewares::PrivateAccess do
+
+ let(:password) { nil }
+ let(:site) { instance_double('Site', name: 'Acme Corp', private_access: private_access, password: password) }
+ let(:url) { 'http://models.example.com' }
+ let(:locomotive_path) { nil }
+ let(:session) { {} }
+ let(:app) { ->(env) { [200, env, ['app']] } }
+ let(:middleware) { described_class.new(app) }
+ let(:rack_env) { build_env }
+ let(:form) { nil }
+
+ subject { code, env, body = middleware.call(rack_env); body.first }
+
+ describe 'no private access enabled' do
+
+ let(:private_access) { false }
+
+ it { is_expected.to eq 'app' }
+
+ end
+
+ describe 'private access enabled' do
+
+ let(:private_access) { true }
+
+ context 'no password defined' do
+
+ it { is_expected.not_to eq 'app' }
+
+ end
+
+ context 'password defined' do
+
+ let(:password) { 'easyone' }
+ let(:form) { 'private_access_password=easyone' }
+
+ describe 'right password submitted' do
+
+ it { is_expected.to eq 'app' }
+ it { subject; expect(session[:private_access_password]).to eq 'easyone' }
+
+ end
+
+ describe 'right password already stored in the session' do
+
+ let(:form) { '' }
+ let(:session) { { private_access_password: 'easyone' } }
+
+ it { is_expected.to eq 'app' }
+ it { subject; expect(session[:private_access_password]).to eq 'easyone' }
+
+ end
+
+ describe 'wrong password submitted' do
+
+ let(:password) { 'easyone' }
+ let(:form) { 'private_access_password=wrongone' }
+
+ it { is_expected.to match /Wrong password/ }
+
+ end
+
+ describe 'feature disabled by a specific rack env variable' do
+
+ let(:form) { '' }
+
+ before { rack_env['steam.private_access_disabled'] = true }
+
+ it { is_expected.to eq 'app' }
+
+ end
+
+ end
+
+ end
+
+ def build_env
+ env_for(url, params: form).tap do |env|
+ env['steam.site'] = site
+ env['steam.request'] = Rack::Request.new(env)
+ env['locomotive.path'] = locomotive_path
+ env['rack.session'] = session
+ end
+ end
+
+ end