Merge branch 'master' into feature-receive

Conflicts:
	app/helpers/finance/order_articles_helper.rb
This commit is contained in:
wvengen 2014-01-09 18:42:29 +01:00
commit b30b424540
26 changed files with 247 additions and 69 deletions

1
.gitignore vendored
View file

@ -18,3 +18,4 @@ Capfile
config/deploy.rb config/deploy.rb
config/deploy/* config/deploy/*
.localeapp .localeapp
.bundle

View file

@ -23,6 +23,8 @@ PATH
specs: specs:
foodsoft_wiki (0.0.1) foodsoft_wiki (0.0.1)
acts_as_versioned acts_as_versioned
content_for_in_controllers
diffy
rails (~> 3.2.15) rails (~> 3.2.15)
wikicloth wikicloth
@ -61,12 +63,12 @@ GEM
activerecord (>= 3.0.0) activerecord (>= 3.0.0)
afm (0.2.0) afm (0.2.0)
arel (3.0.3) arel (3.0.3)
better_errors (1.0.1) better_errors (1.1.0)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubis (>= 2.6.6) erubis (>= 2.6.6)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootstrap-datepicker-rails (1.1.1.10) bootstrap-datepicker-rails (1.1.1.11)
railties (>= 3.0) railties (>= 3.0)
builder (3.0.4) builder (3.0.4)
bullet (4.7.1) bullet (4.7.1)
@ -80,7 +82,7 @@ GEM
net-ssh-gateway (>= 1.1.0) net-ssh-gateway (>= 1.1.0)
capistrano-ext (1.2.1) capistrano-ext (1.2.1)
capistrano (>= 1.0.0) capistrano (>= 1.0.0)
capybara (2.2.0) capybara (2.2.1)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
@ -102,6 +104,7 @@ GEM
execjs execjs
coffee-script-source (1.6.3) coffee-script-source (1.6.3)
commonjs (0.2.7) commonjs (0.2.7)
content_for_in_controllers (0.0.2)
coveralls (0.7.0) coveralls (0.7.0)
multi_json (~> 1.3) multi_json (~> 1.3)
rest-client rest-client
@ -112,6 +115,7 @@ GEM
database_cleaner (1.2.0) database_cleaner (1.2.0)
debug_inspector (0.0.2) debug_inspector (0.0.2)
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.1)
docile (1.1.1) docile (1.1.1)
erubis (2.7.0) erubis (2.7.0)
eventmachine (1.0.3) eventmachine (1.0.3)
@ -128,7 +132,7 @@ GEM
faker (1.2.0) faker (1.2.0)
i18n (~> 0.5) i18n (~> 0.5)
ffi (1.9.3) ffi (1.9.3)
haml (4.0.4) haml (4.0.5)
tilt tilt
haml-rails (0.4) haml-rails (0.4)
actionpack (>= 3.1, < 4.1) actionpack (>= 3.1, < 4.1)
@ -203,8 +207,7 @@ GEM
polyamorous (0.5.0) polyamorous (0.5.0)
activerecord (~> 3.0) activerecord (~> 3.0)
polyglot (0.3.3) polyglot (0.3.3)
prawn (0.13.0) prawn (0.13.2)
afm
pdf-reader (~> 1.2) pdf-reader (~> 1.2)
ruby-rc4 ruby-rc4
ttfunk (~> 1.0.3) ttfunk (~> 1.0.3)
@ -241,7 +244,7 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
rdoc (~> 3.4) rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0) thor (>= 0.14.6, < 2.0)
rake (10.1.0) rake (10.1.1)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redis (3.0.6) redis (3.0.6)
@ -258,6 +261,7 @@ GEM
vegas (~> 0.1.2) vegas (~> 0.1.2)
rest-client (1.6.7) rest-client (1.6.7)
mime-types (>= 1.16) mime-types (>= 1.16)
rinku (1.7.3)
rspec (2.14.1) rspec (2.14.1)
rspec-core (~> 2.14.0) rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0) rspec-expectations (~> 2.14.0)
@ -266,8 +270,9 @@ GEM
rspec-expectations (2.14.4) rspec-expectations (2.14.4)
diff-lcs (>= 1.1.3, < 2.0) diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.4) rspec-mocks (2.14.4)
rspec-rails (2.14.0) rspec-rails (2.14.1)
actionpack (>= 3.0) actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 2.14.0) rspec-core (~> 2.14.0)
@ -275,10 +280,10 @@ GEM
rspec-mocks (~> 2.14.0) rspec-mocks (~> 2.14.0)
rspec-rerun (0.1.3) rspec-rerun (0.1.3)
rspec (>= 2.11.0) rspec (>= 2.11.0)
ruby-prof (0.13.1) ruby-prof (0.14.2)
ruby-rc4 (0.1.5) ruby-rc4 (0.1.5)
rubyzip (1.1.0) rubyzip (1.1.0)
sass (3.2.12) sass (3.2.13)
sass-rails (3.2.6) sass-rails (3.2.6)
railties (~> 3.2.0) railties (~> 3.2.0)
sass (>= 3.1.10) sass (>= 3.1.10)
@ -338,7 +343,7 @@ GEM
rails (>= 3.1) rails (>= 3.1)
railties (>= 3.1) railties (>= 3.1)
tzinfo (0.3.38) tzinfo (0.3.38)
uglifier (2.3.3) uglifier (2.4.0)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)
uniform_notifier (1.4.0) uniform_notifier (1.4.0)
@ -348,9 +353,10 @@ GEM
whenever (0.9.0) whenever (0.9.0)
activesupport (>= 2.3.4) activesupport (>= 2.3.4)
chronic (>= 0.6.3) chronic (>= 0.6.3)
wikicloth (0.8.0) wikicloth (0.8.1)
builder builder
expression_parser expression_parser
rinku
xpath (2.0.0) xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)

View file

@ -1,6 +1,7 @@
FoodSoft FoodSoft
========= =========
[![Build Status](https://travis-ci.org/foodcoops/foodsoft.png)](https://travis-ci.org/foodcoops/foodsoft) [![Build Status](https://travis-ci.org/foodcoops/foodsoft.png?branch=master)](https://travis-ci.org/foodcoops/foodsoft)
[![Coverage Status](https://coveralls.io/repos/foodcoops/foodsoft/badge.png?branch=master)](https://coveralls.io/r/foodcoops/foodsoft?branch=master)
[![Code Climate](https://codeclimate.com/github/foodcoops/foodsoft.png)](https://codeclimate.com/github/foodcoops/foodsoft) [![Code Climate](https://codeclimate.com/github/foodcoops/foodsoft.png)](https://codeclimate.com/github/foodcoops/foodsoft)
[![Dependency Status](https://gemnasium.com/foodcoops/foodsoft.png)](https://gemnasium.com/foodcoops/foodsoft) [![Dependency Status](https://gemnasium.com/foodcoops/foodsoft.png)](https://gemnasium.com/foodcoops/foodsoft)
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/foodcoops/foodsoft/trend.png)](https://bitdeli.com/foodcoops "Bitdeli Badge") [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/foodcoops/foodsoft/trend.png)](https://bitdeli.com/foodcoops "Bitdeli Badge")

View file

@ -26,11 +26,11 @@ class ApplicationController < ActionController::Base
def deny_access def deny_access
session[:return_to] = request.original_url session[:return_to] = request.original_url
redirect_to login_url, :alert => I18n.t('application.controller.error_denied') redirect_to root_url, alert: I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path))
end end
private private
def authenticate(role = 'any') def authenticate(role = 'any')
# Attempt to retrieve authenticated user from controller instance or session... # Attempt to retrieve authenticated user from controller instance or session...
if !current_user if !current_user
@ -92,6 +92,18 @@ class ApplicationController < ActionController::Base
end end
end end
def authenticate_or_token(prefix, role = 'any')
if not params[:token].blank?
begin
TokenVerifier.new(prefix).verify(params[:token])
rescue ActiveSupport::MessageVerifier::InvalidSignature
redirect_to root_path, alert: I18n.t('application.controller.error_token')
end
else
authenticate(role)
end
end
# Stores this controller instance as a thread local varibale to be accessible from outside ActionController/ActionView. # Stores this controller instance as a thread local varibale to be accessible from outside ActionController/ActionView.
def store_controller def store_controller
Thread.current[:application_controller] = self Thread.current[:application_controller] = self

View file

@ -28,6 +28,7 @@ class SessionsController < ApplicationController
def destroy def destroy
session[:user_id] = nil session[:user_id] = nil
session[:return_to] = nil
redirect_to login_url, :notice => I18n.t('sessions.logged_out') redirect_to login_url, :notice => I18n.t('sessions.logged_out')
end end
end end

View file

@ -7,35 +7,14 @@
.modal-body .modal-body
= f.input :availability = f.input :availability
= f.input :name = f.input :name
.fold-line
= f.input :unit_quantity, label: Article.human_attribute_name(:unit), = render partial: 'shared/article_fields_units', locals: {f: f}
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit_quantity)}
= f.input :unit, label: '&times;'.html_safe,
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit)}
= f.input :note = f.input :note
= f.association :article_category = f.association :article_category
/ TODO labels / TODO labels
.fold-line = render partial: 'shared/article_fields_price', locals: {f: f}
= f.input :price do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :price, class: 'input-mini'
= f.input :tax do
.input-append
= f.input_field :tax, class: 'input-mini'
%span.add-on %
.fold-line
= f.input :deposit do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :deposit, class: 'input-mini'
.control-group
%label.control-label{for: 'article_fc_price'}
= Article.human_attribute_name(:fc_price)
.controls.control-text#article_fc_price
= number_to_currency(@article.fc_price) rescue nil
= f.input :origin = f.input :origin
= f.input :manufacturer = f.input :manufacturer
@ -44,14 +23,3 @@
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'} = link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
= f.submit class: 'btn btn-primary' = f.submit class: 'btn btn-primary'
:javascript
var form = $('form.edit_article, form.new_article');
$('#article_price, #article_tax, #article_deposit', form).on('change keyup', function() {
var price = parseFloat($('#article_price', form).val());
var tax = parseFloat($('#article_tax', form).val());
var deposit = parseFloat($('#article_deposit', form).val());
// Article#gross_price and Article#fc_price
var gross_price = (price + deposit) * (tax / 100 + 1);
var fc_price = gross_price * (#{FoodsoftConfig[:price_markup].to_f} / 100 + 1);
$('#article_fc_price').html($.isNumeric(fc_price) ? I18n.l("currency", fc_price) : '&#133;');
});

View file

@ -8,7 +8,7 @@
= csrf_meta_tags = csrf_meta_tags
= stylesheet_link_tag "application", :media => "all" = stylesheet_link_tag "application", :media => "all"
//%link(href="images/favicon.ico" rel="shortcut icon") //%link(href="images/favicon.ico" rel="shortcut icon")
= yield(:head) = yield(:head)
%body %body

View file

@ -1,6 +1,6 @@
- title t('.title') - title t('.title')
= raw t('.body', user: h(show_user(@user))) = raw t('.body', user: h(show_user(@user)))
= simple_form_for @user, :url => update_password_path(@user.id, :token => @user.reset_password_token) do |form| = simple_form_for @user, :url => {:action => 'update_password', :id => @user.id, :token => @user.reset_password_token} do |form|
= form.input :password = form.input :password
= form.input :password_confirmation = form.input :password_confirmation
.form-actions .form-actions

View file

@ -17,18 +17,16 @@
= simple_fields_for :article, @order_article.article do |f| = simple_fields_for :article, @order_article.article do |f|
= f.input :name = f.input :name
= f.input :order_number
= f.input :unit
- if @order_article.article.is_a?(StockArticle) - if @order_article.article.is_a?(StockArticle)
%div.alert= t '.stock_alert' %div.alert= t '.stock_alert'
- else - else
= simple_fields_for :article_price, @order_article.article_price do |f| = simple_fields_for :article_price, @order_article.article_price do |fprice|
= f.input :unit_quantity = render partial: 'shared/article_fields_units', locals: {f_unit: f, f_uq: fprice}
= f.input :price = render partial: 'shared/article_fields_price', locals: {f: fprice}
= f.input :tax
= f.input :deposit
= form.input :update_current_price, as: :boolean = form.input :update_current_price, as: :boolean
= f.input :order_number
.modal-footer .modal-footer
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'} = link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
= form.submit class: 'btn btn-primary' = form.submit class: 'btn btn-primary'

View file

@ -0,0 +1,34 @@
.fold-line
= f.input :price do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :price, class: 'input-mini'
= f.input :tax do
.input-append
= f.input_field :tax, class: 'input-mini'
%span.add-on %
.fold-line
= f.input :deposit do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= f.input_field :deposit, class: 'input-mini'
.control-group
%label.control-label{for: 'article_fc_price'}
= Article.human_attribute_name(:fc_price)
.controls.control-text#article_fc_price
= number_to_currency(f.object.fc_price) rescue nil
-# do this inline, since it's being used in ajax forms only
- field = f.object.class.model_name.underscore
:javascript
var form = $('#article_fc_price').closest('form');
$('##{field}_price, ##{field}_tax, ##{field}_deposit', form).on('change keyup', function() {
var price = parseFloat($('##{field}_price', form).val());
var tax = parseFloat($('##{field}_tax', form).val());
var deposit = parseFloat($('##{field}_deposit', form).val());
// Article#gross_price and Article#fc_price
var gross_price = (price + deposit) * (tax / 100 + 1);
var fc_price = gross_price * (#{FoodsoftConfig[:price_markup].to_f} / 100 + 1);
$('#article_fc_price').html($.isNumeric(fc_price) ? I18n.l("currency", fc_price) : '&#133;');
});

View file

@ -0,0 +1,6 @@
-# use the local 'f', or supply 'f_uq' and 'f_unit' for more control (like in balancing)
.fold-line
= (f_uq rescue f).input :unit_quantity, label: Article.human_attribute_name(:unit),
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit_quantity)}
= (f_unit rescue f).input :unit, label: '&times;'.html_safe,
input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit)}

View file

@ -281,7 +281,8 @@ de:
application: application:
controller: controller:
error_authn: Anmeldung erforderlich! error_authn: Anmeldung erforderlich!
error_denied: Kein Zugriff! error_denied: Du darfst die gewünschte Seite nicht sehen. Wenn Du denkst, dass Du dürfen solltest, frage eine Administratorin, dass sie Dir die entsprechenden Rechte einräumt. Falls Du Zugang zu mehreren Benutzerkonten hast, möchtest Du Dich vielleicht %{sign_in}.
error_denied_sign_in: als ein anderer Benutzer anmelden
error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt! error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!
article_categories: article_categories:
create: create:

View file

@ -287,8 +287,10 @@ en:
application: application:
controller: controller:
error_authn: Authentication required! error_authn: Authentication required!
error_denied: Access denied! error_denied: You are not allowed to view the requested page. If you think you should, ask an administrator to give you appropriate permissions. If you have access to multiple user accounts, you might want to %{sign_in}.
error_denied_sign_in: sign in as another user
error_members_only: This action is only available to members of the group! error_members_only: This action is only available to members of the group!
error_token: Access denied (invalid token)!
article_categories: article_categories:
create: create:
notice: Category was stored notice: Category was stored

View file

@ -21,7 +21,7 @@ Foodsoft::Application.routes.draw do
get '/login/forgot_password' => 'login#forgot_password', as: :forgot_password get '/login/forgot_password' => 'login#forgot_password', as: :forgot_password
post '/login/reset_password' => 'login#reset_password', as: :reset_password post '/login/reset_password' => 'login#reset_password', as: :reset_password
get '/login/new_password' => 'login#new_password', as: :new_password get '/login/new_password' => 'login#new_password', as: :new_password
get '/login/update_password' => 'login#update_password', as: :update_password put '/login/update_password' => 'login#update_password', as: :update_password
match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation
resources :sessions, :only => [:new, :create, :destroy] resources :sessions, :only => [:new, :create, :destroy]

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,6 +1,14 @@
# encoding: utf-8 # encoding: utf-8
class PagesController < ApplicationController class PagesController < ApplicationController
skip_before_filter :authenticate, :only => :all
before_filter :only => :all do
authenticate_or_token(['wiki', 'all'])
end
before_filter do
content_for :head, view_context.rss_meta_tag
end
def index def index
@page = Page.find_by_permalink "Home" @page = Page.find_by_permalink "Home"
@ -114,6 +122,10 @@ class PagesController < ApplicationController
end end
@pages.order(order) @pages.order(order)
end end
respond_to do |format|
format.html
format.rss { render :layout => false }
end
end end
def version def version

View file

@ -1,6 +1,10 @@
module PagesHelper module PagesHelper
include WikiCloth include WikiCloth
def rss_meta_tag
tag('link', :rel => "alternate", :type => "application/rss+xml", :title => "RSS", :href => all_pages_rss_url).html_safe
end
def wikified_body(body, title = nil) def wikified_body(body, title = nil)
render_opts = {:locale => I18n.locale} # workaround for wikicloth 0.8.0 https://github.com/nricciar/wikicloth/pull/59 render_opts = {:locale => I18n.locale} # workaround for wikicloth 0.8.0 https://github.com/nricciar/wikicloth/pull/59
WikiCloth.new({:data => body+"\n", :link_handler => Wikilink.new, :params => {:referer => title}}).to_html(render_opts).html_safe WikiCloth.new({:data => body+"\n", :link_handler => Wikilink.new, :params => {:referer => title}}).to_html(render_opts).html_safe
@ -59,4 +63,10 @@ module PagesHelper
Array.new Array.new
end end
end end
# return url for all_pages rss feed
def all_pages_rss_url(options={})
token = TokenVerifier.new(['wiki', 'all']).generate
all_pages_url({:format => 'rss', :token => token}.merge(options))
end
end end

View file

@ -33,6 +33,24 @@ class Page < ActiveRecord::Base
self.permalink = Page.count == 0 ? "Home" : Page.permalink(title) self.permalink = Page.count == 0 ? "Home" : Page.permalink(title)
end end
end end
def diff
current = versions.latest
old = versions.where(["page_id = ? and lock_version < ?", current.page_id, current.lock_version]).order('lock_version DESC').first
if old
o = ''
Diffy::Diff.new(old.body, current.body).each do |line|
case line
when /^\+/ then o += "#{line.chomp}<br />" unless line.chomp == "+"
when /^-/ then o += "#{line.chomp}<br />" unless line.chomp == "-"
end
end
o
else
current.body
end
end
protected protected

View file

@ -9,6 +9,7 @@
%li= link_to t('.recent_changes'), all_pages_path(:view => 'recent_changes') %li= link_to t('.recent_changes'), all_pages_path(:view => 'recent_changes')
%li= link_to t('.title_list'), all_pages_path(:view => 'title_list') %li= link_to t('.title_list'), all_pages_path(:view => 'title_list')
%li= link_to t('.site_map'), all_pages_path(:view => 'site_map') %li= link_to t('.site_map'), all_pages_path(:view => 'site_map')
%li= link_to image_tag('icons/feed-icon-14x14.png', :alt => 'RSS Feed'), all_pages_rss_url
= form_tag all_pages_path, method: :get, class: 'form-search pull-right' do = form_tag all_pages_path, method: :get, class: 'form-search pull-right' do
= text_field_tag :name, params[:name], class: 'input-medium search-query', = text_field_tag :name, params[:name], class: 'input-medium search-query',
placeholder: t('.search.placeholder') placeholder: t('.search.placeholder')

View file

@ -0,0 +1,19 @@
xml.instruct! :xml, :version => "1.0"
xml.rss :version => "2.0" do
xml.channel do
xml.title FoodsoftConfig[:name] + " Wiki"
xml.description ""
xml.link FoodsoftConfig[:homepage]
for page in @pages
xml.item do
xml.title page.title
xml.description page.diff, :type => "html"
xml.author User.find(page.updated_by).display
xml.pubDate page.updated_at.to_s(:rfc822)
xml.link wiki_page_path(page.permalink)
xml.guid page.updated_at.to_i
end
end
end
end

View file

@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.add_dependency "rails", "~> 3.2.15" s.add_dependency "rails", "~> 3.2.15"
s.add_dependency 'wikicloth' s.add_dependency 'wikicloth'
s.add_dependency 'acts_as_versioned' # need git version, make sure that is included in foodsoft's Gemfile s.add_dependency 'acts_as_versioned' # need git version, make sure that is included in foodsoft's Gemfile
s.add_dependency 'diffy'
s.add_dependency 'content_for_in_controllers'
s.add_development_dependency "sqlite3" s.add_development_dependency "sqlite3"
end end

View file

@ -1,5 +1,7 @@
require 'wikicloth' require 'wikicloth'
require 'acts_as_versioned' require 'acts_as_versioned'
require 'diffy'
require 'content_for_in_controllers'
require 'foodsoft_wiki/engine' require 'foodsoft_wiki/engine'
module FoodsoftWiki module FoodsoftWiki

View file

@ -1,3 +1,5 @@
require 'stringio'
# put in here all foodsoft tasks # put in here all foodsoft tasks
# => :environment loads the environment an gives easy access to the application # => :environment loads the environment an gives easy access to the application
@ -23,8 +25,6 @@ module Colors
end end
include Colors include Colors
require 'stringio'
namespace :foodsoft do namespace :foodsoft do
desc "Setup foodsoft" desc "Setup foodsoft"
task :setup_development do task :setup_development do
@ -82,10 +82,11 @@ end
def setup_app_config def setup_app_config
file = 'config/app_config.yml' file = 'config/app_config.yml'
sample = Rails.root.join("#{file}.SAMPLE")
return nil if skip?(file) return nil if skip?(file)
puts yellow "Copying #{file}..." puts yellow "Copying #{file}..."
%x( cp #{Rails.root.join("#{file}.SAMPLE")} #{Rails.root.join(file)} ) %x( cp #{sample} #{Rails.root.join(file)} )
reminder(file) reminder(file)
end end
@ -120,6 +121,8 @@ def start_server
puts blue "Start your server running 'bundle exec rails s' and visit http://localhost:3000" puts blue "Start your server running 'bundle exec rails s' and visit http://localhost:3000"
end end
# Helper Methods
def ask(question, answers = false) def ask(question, answers = false)
puts question puts question
input = STDIN.gets.chomp input = STDIN.gets.chomp
@ -149,3 +152,4 @@ def capture_stdout
ensure ensure
$stdout = STDOUT $stdout = STDOUT
end end

37
lib/token_verifier.rb Normal file
View file

@ -0,0 +1,37 @@
class TokenVerifier < ActiveSupport::MessageVerifier
def initialize(prefix)
super(self.class.secret)
@_prefix = prefix.is_a?(Array) ? prefix.join(':') : prefix.to_s
end
def generate(message=nil)
fullmessage = [FoodsoftConfig.scope, @_prefix]
fullmessage.append(message) unless message.nil?
super(fullmessage)
end
def verify(message)
r = super(message)
raise InvalidMessage unless r.is_a?(Array) and r.length >= 2 and r.length <= 3
raise InvalidScope unless r[0] == FoodsoftConfig.scope
raise InvalidPrefix unless r[1] == @_prefix
# return original message
if r.length > 2
r[2]
else
nil
end
end
class InvalidMessage < ActiveSupport::MessageVerifier::InvalidSignature; end
class InvalidScope < ActiveSupport::MessageVerifier::InvalidSignature; end
class InvalidPrefix < ActiveSupport::MessageVerifier::InvalidSignature; end
protected
def self.secret
Foodsoft::Application.config.secret_token
end
end

View file

@ -0,0 +1,44 @@
require_relative '../spec_helper'
describe TokenVerifier do
let (:prefix) { 'xyz' }
let (:v) { TokenVerifier.new(prefix) }
let (:msg) { v.generate }
it 'validates' do
expect{ v.verify(msg) }.to_not raise_error
end
it 'validates when recreated' do
v2 = TokenVerifier.new(prefix)
expect{ v2.verify(msg) }.to_not raise_error
end
it 'does not validate with a different prefix' do
v2 = TokenVerifier.new('abc')
expect { v2.verify(msg) }.to raise_error(TokenVerifier::InvalidPrefix)
end
it 'does not validate in a different foodcoop scope' do
msg
oldscope = FoodsoftConfig.scope
begin
FoodsoftConfig.scope = Faker::Lorem.words(1)
v2 = TokenVerifier.new(prefix)
expect{ v2.verify(msg) }.to raise_error(TokenVerifier::InvalidScope)
ensure
FoodsoftConfig.scope = oldscope
end
end
it 'does not validate a random string' do
expect{ v.verify(Faker::Lorem.characters(100)) }.to raise_error(ActiveSupport::MessageVerifier::InvalidSignature)
end
it 'returns the message' do
data = [5, {'hi' => :there}, 'bye', []]
msg = v.generate(data)
expect(v.verify(msg)).to eq data
end
end