new liquid tag: authorize (redirect to a page if an user is not authenticated)

did committed Dec 20, 2016
commit 7ea2f860275a9403d521368fe6bd343007d6bccd
Showing 8 changed files with 247 additions and 1 deletions
locomotive/steam/errors.rb b/lib/locomotive/steam/errors.rb +4 -0
@@ @@ -6,6 +6,10 @@ module Locomotive::Steam
class ActionException < ::Exception
end
+ class RedirectionException < ::Exception
+ alias url message
+ end
+
class RenderError < ::StandardError
LINES_RANGE = 10
locomotive/steam/liquid/tags/authorize.rb b/lib/locomotive/steam/liquid/tags/authorize.rb +72 -0
@@ @@ -0,0 +1,72 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ # Redirect the current site visitor to another page if she/he
+ # is not authenticated.
+ # More information about the authentication feature here:
+ # https://locomotive-v3.readme.io/v3.3/docs/introduction-1
+ #
+ # The Liquid tag requires 2 parameters:
+ # - the slug of the content type used for the authentication (a content type with a password field)
+ # - the handle of the page we want the user to be redirected to if unauthenticated
+ #
+ # Basically the authorize tag checks if the liquid context has
+ # a reference to the current authenticated content entry.
+ # If not, it raises a redirection exception forcing the Steam middleware stack
+ # to process a HTTP redirection.
+ #
+ # Example:
+ #
+ # {% authorize 'accounts', 'sign_in' %}
+ #
+ class Authorize < ::Liquid::Tag
+
+ Syntax = /(#{::Liquid::QuotedString}+)\s*,\s*(#{::Liquid::QuotedString}+)/o
+
+ def initialize(tag_name, markup, options)
+ if markup =~ Syntax
+ @content_type_slug, @page_handle = $1.try(:gsub, /['"]/, ''), $2.try(:gsub, /['"]/, '')
+ else
+ raise ::Liquid::SyntaxError.new("Syntax Error in 'authorize' - Valid syntax: authorize [content type slug], [page handle]")
+ end
+
+ super
+ end
+
+ def render(context)
+ @context = context
+
+ unless authenticated_entry = context["current_#{@content_type_slug.singularize}"]
+ raise Locomotive::Steam::RedirectionException.new(page_url)
+ end
+ ''
+ end
+
+ private
+
+ def page_url
+ if page = services.page_finder.by_handle(@page_handle)
+ services.url_builder.url_for(page, locale)
+ else
+
+ end
+ end
+
+ def locale
+ @context.registers[:locale]
+ end
+
+ def services
+ @context.registers[:services]
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('authorize'.freeze, Authorize)
+
+ end
+ end
+ end
+ end
locomotive/steam/middlewares/redirection.rb b/lib/locomotive/steam/middlewares/redirection.rb +24 -0
@@ @@ -0,0 +1,24 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # When rendering the page, the developer can stop it at anytime by
+ # raising an RedirectionException exception. The exception message holds
+ # the url we want the user to be redirected to.
+ # This is specifically used by the authorize liquid tag.
+ #
+ class Redirection < ThreadSafe
+
+ include Helpers
+
+ def _call
+ begin
+ self.next
+ rescue Locomotive::Steam::RedirectionException => e
+ redirect_to e.message, 302
+ end
+ end
+
+ end
+ end
+
+ end
locomotive/steam/server.rb b/lib/locomotive/steam/server.rb +1 -1
@@ @@ -58,10 +58,10 @@ module Locomotive::Steam
Middlewares::UrlRedirection,
Middlewares::Robots,
Middlewares::Timezone,
- # Middlewares::Warden,
Middlewares::EntrySubmission,
Middlewares::Locale,
Middlewares::LocaleRedirection,
+ Middlewares::Redirection,
Middlewares::Auth,
Middlewares::PrivateAccess,
Middlewares::Path,
spec/unit/liquid/tags/authorize_spec.rb +51 -0
@@ @@ -0,0 +1,51 @@
+ require 'spec_helper'
+
+ describe Locomotive::Steam::Liquid::Tags::Authorize do
+
+ let(:site) { instance_double('Site', default_locale: 'en', prefix_default_locale: false) }
+ let(:page) { instance_double('Page', fullpath: 'me/sign_in', templatized?: false) }
+ let(:page_handle) { "'sign_in'" }
+ let(:source) { "{% authorize 'accounts', #{page_handle} %}Hello world!" }
+ let(:assigns) { {} }
+ let(:services) { Locomotive::Steam::Services.build_instance }
+ let(:context) { ::Liquid::Context.new(assigns, {}, { services: services }) }
+
+ before {
+ allow(services).to receive(:current_site).and_return(site)
+ allow(services.page_finder).to receive(:by_handle).and_return(page)
+ }
+
+ subject { render_template(source, context) }
+
+ describe 'validating syntax' do
+
+ describe 'no page handle' do
+ let(:source) { '{% authorize accounts %}' }
+ it { expect { subject }.to raise_exception(Liquid::SyntaxError) }
+ end
+
+ end
+
+ describe '#render' do
+
+ context 'unauthenticated account' do
+
+ it 'redirects to the sign in page' do
+ expect { subject }.to raise_error(Locomotive::Steam::RedirectionException, '/me/sign_in')
+ end
+
+ end
+
+ context 'authenticated account' do
+
+ let(:assigns) { { 'current_account' => liquid_instance_double('Account', {}) } }
+
+ it 'renders the page' do
+ expect(subject).to eq 'Hello world!'
+ end
+
+ end
+
+ end
+
+ end
spec/unit/middlewares/auth_spec.rb +31 -0
@@ @@ -0,0 +1,31 @@
+ require 'spec_helper'
+
+ require_relative '../../../lib/locomotive/steam/middlewares/thread_safe'
+ require_relative '../../../lib/locomotive/steam/middlewares/helpers'
+ require_relative '../../../lib/locomotive/steam/middlewares/auth'
+
+ describe Locomotive::Steam::Middlewares::Auth::AuthOptions do
+
+ let(:metafields) { { 'smtp' => { 'address' => '127.0.0.1', 'user_name' => 'John', 'password' => 'doe', 'port' => 25 } } }
+ let(:site) { instance_double('Site', metafields: metafields) }
+ let(:params) { {} }
+
+ let(:options) { described_class.new(site, params) }
+
+ describe '#smtp' do
+
+ subject { options.smtp }
+
+ it { is_expected.to eq('address': '127.0.0.1', 'user_name': 'John', 'password': 'doe', 'port': 25) }
+
+ context 'no smtp metafields' do
+
+ let(:metafields) { {} }
+
+ it { is_expected.to eq({}) }
+
+ end
+
+ end
+
+ end
spec/unit/middlewares/redirection_spec.rb +37 -0
@@ @@ -0,0 +1,37 @@
+ require 'spec_helper'
+
+ require_relative '../../../lib/locomotive/steam/middlewares/thread_safe'
+ require_relative '../../../lib/locomotive/steam/middlewares/helpers'
+ require_relative '../../../lib/locomotive/steam/middlewares/redirection'
+
+ describe Locomotive::Steam::Middlewares::Redirection do
+
+ let(:site) { instance_double('Site') }
+ let(:url) { 'http://models.example.com/about-us' }
+ let(:locomotive_path) { nil }
+ let(:app) { ->(env) { [200, env, 'app'] } }
+ let(:middleware) { described_class.new(app) }
+
+ subject do
+ env = env_for(url, 'steam.site' => site)
+ env['steam.request'] = Rack::Request.new(env)
+ env['locomotive.path'] = locomotive_path
+ code, env = middleware.call(env)
+ [code, env['Location']]
+ end
+
+ describe 'no redirection exception raised' do
+
+ it { is_expected.to eq [200, nil] }
+
+ end
+
+ describe 'redirection exception raised' do
+
+ let(:app) { ->(env) { raise Locomotive::Steam::RedirectionException.new('/sign_in') } }
+
+ it { is_expected.to eq [302, '/sign_in'] }
+
+ end
+
+ end
spec/unit/services/auth_service_spec.rb +27 -0
@@ @@ -71,6 +71,33 @@ describe Locomotive::Steam::AuthService do
expect(liquid_context['reset_password_url']).to eq '/reset-password?auth_reset_token=42a'
end
+ context 'no email template' do
+
+ let(:_auth_options) { default_auth_options.merge(email_handle: nil) }
+ let(:auth_options) { instance_double('AuthOptions', _auth_options) }
+
+ it 'also sends the instructions by email with a default email template' do
+ allow(SecureRandom).to receive(:hex).and_return('42a')
+ entry = build_account('easyone', '42a')
+ expect(entries).to receive(:all).with('accounts', { 'email' => 'john@doe.net' }).and_return([entry])
+ expect(entries).to receive(:update_decorated_entry)
+ expect(emails).to receive(:send_email).with({
+ from: 'contact@acme.org',
+ to: 'john@doe.net',
+ subject: 'Instructions for changing your password',
+ body: (<<-EMAIL
+ Hi,
+ To reset your password please follow the link below: /reset-password?auth_reset_token=42a.
+ Thanks!
+ EMAIL
+ ),
+ smtp: {} }, liquid_context)
+ is_expected.to eq :reset_password_instructions_sent
+ expect(liquid_context['reset_password_url']).to eq '/reset-password?auth_reset_token=42a'
+ end
+
+ end
+
end
describe '#reset_password' do