diff --git a/Gemfile.lock b/Gemfile.lock
index 291014eb..aec872f9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -23,6 +23,8 @@ PATH
specs:
foodsoft_wiki (0.0.1)
acts_as_versioned
+ content_for_in_controllers
+ diffy
rails (~> 3.2.15)
wikicloth
@@ -102,6 +104,7 @@ GEM
execjs
coffee-script-source (1.6.3)
commonjs (0.2.7)
+ content_for_in_controllers (0.0.2)
coveralls (0.7.0)
multi_json (~> 1.3)
rest-client
@@ -112,6 +115,7 @@ GEM
database_cleaner (1.2.0)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
+ diffy (3.0.1)
docile (1.1.1)
erubis (2.7.0)
eventmachine (1.0.3)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 63cb66bc..85a7ccef 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -30,7 +30,7 @@ class ApplicationController < ActionController::Base
end
private
-
+
def authenticate(role = 'any')
# Attempt to retrieve authenticated user from controller instance or session...
if !current_user
@@ -87,6 +87,18 @@ class ApplicationController < ActionController::Base
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.
def store_controller
Thread.current[:application_controller] = self
diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml
index ab2e7d8e..06920588 100644
--- a/app/views/layouts/_header.html.haml
+++ b/app/views/layouts/_header.html.haml
@@ -8,7 +8,7 @@
= csrf_meta_tags
= stylesheet_link_tag "application", :media => "all"
//%link(href="images/favicon.ico" rel="shortcut icon")
-
+
= yield(:head)
%body
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 86fea3bb..9ddfecfa 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -277,6 +277,7 @@ en:
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_token: Access denied (invalid token)!
article_categories:
create:
notice: Category was stored
diff --git a/lib/foodsoft_wiki/app/assets/images/icons/feed-icon-14x14.png b/lib/foodsoft_wiki/app/assets/images/icons/feed-icon-14x14.png
new file mode 100755
index 00000000..b3c949d2
Binary files /dev/null and b/lib/foodsoft_wiki/app/assets/images/icons/feed-icon-14x14.png differ
diff --git a/lib/foodsoft_wiki/app/assets/images/icons/feed-icon-28x28.png b/lib/foodsoft_wiki/app/assets/images/icons/feed-icon-28x28.png
new file mode 100755
index 00000000..d64c669c
Binary files /dev/null and b/lib/foodsoft_wiki/app/assets/images/icons/feed-icon-28x28.png differ
diff --git a/lib/foodsoft_wiki/app/controllers/pages_controller.rb b/lib/foodsoft_wiki/app/controllers/pages_controller.rb
index ed4598b4..39ca7c62 100644
--- a/lib/foodsoft_wiki/app/controllers/pages_controller.rb
+++ b/lib/foodsoft_wiki/app/controllers/pages_controller.rb
@@ -1,6 +1,14 @@
# encoding: utf-8
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
@page = Page.find_by_permalink "Home"
@@ -114,6 +122,10 @@ class PagesController < ApplicationController
end
@pages.order(order)
end
+ respond_to do |format|
+ format.html
+ format.rss { render :layout => false }
+ end
end
def version
diff --git a/lib/foodsoft_wiki/app/helpers/pages_helper.rb b/lib/foodsoft_wiki/app/helpers/pages_helper.rb
index cfc8ea7b..1f7605e5 100644
--- a/lib/foodsoft_wiki/app/helpers/pages_helper.rb
+++ b/lib/foodsoft_wiki/app/helpers/pages_helper.rb
@@ -1,6 +1,10 @@
module PagesHelper
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)
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
@@ -59,4 +63,10 @@ module PagesHelper
Array.new
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
diff --git a/lib/foodsoft_wiki/app/models/page.rb b/lib/foodsoft_wiki/app/models/page.rb
index 3dfba7aa..7759effe 100644
--- a/lib/foodsoft_wiki/app/models/page.rb
+++ b/lib/foodsoft_wiki/app/models/page.rb
@@ -33,6 +33,24 @@ class Page < ActiveRecord::Base
self.permalink = Page.count == 0 ? "Home" : Page.permalink(title)
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}
" unless line.chomp == "+"
+ when /^-/ then o += "#{line.chomp}
" unless line.chomp == "-"
+ end
+ end
+ o
+ else
+ current.body
+ end
+ end
protected
diff --git a/lib/foodsoft_wiki/app/views/pages/all.html.haml b/lib/foodsoft_wiki/app/views/pages/all.html.haml
index bb57e33c..a3a5ea35 100644
--- a/lib/foodsoft_wiki/app/views/pages/all.html.haml
+++ b/lib/foodsoft_wiki/app/views/pages/all.html.haml
@@ -9,6 +9,7 @@
%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('.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
= text_field_tag :name, params[:name], class: 'input-medium search-query',
placeholder: t('.search.placeholder')
diff --git a/lib/foodsoft_wiki/app/views/pages/all.rss.builder b/lib/foodsoft_wiki/app/views/pages/all.rss.builder
new file mode 100644
index 00000000..35581c72
--- /dev/null
+++ b/lib/foodsoft_wiki/app/views/pages/all.rss.builder
@@ -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
\ No newline at end of file
diff --git a/lib/foodsoft_wiki/foodsoft_wiki.gemspec b/lib/foodsoft_wiki/foodsoft_wiki.gemspec
index 937a887f..f3c50662 100644
--- a/lib/foodsoft_wiki/foodsoft_wiki.gemspec
+++ b/lib/foodsoft_wiki/foodsoft_wiki.gemspec
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.add_dependency "rails", "~> 3.2.15"
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 'diffy'
+ s.add_dependency 'content_for_in_controllers'
s.add_development_dependency "sqlite3"
end
diff --git a/lib/foodsoft_wiki/lib/foodsoft_wiki.rb b/lib/foodsoft_wiki/lib/foodsoft_wiki.rb
index acf5ef50..ddcb0f83 100644
--- a/lib/foodsoft_wiki/lib/foodsoft_wiki.rb
+++ b/lib/foodsoft_wiki/lib/foodsoft_wiki.rb
@@ -1,5 +1,7 @@
require 'wikicloth'
require 'acts_as_versioned'
+require 'diffy'
+require 'content_for_in_controllers'
require 'foodsoft_wiki/engine'
module FoodsoftWiki
diff --git a/lib/tasks/foodsoft_setup.rake b/lib/tasks/foodsoft_setup.rake
index 5e7ba088..13c91ac2 100644
--- a/lib/tasks/foodsoft_setup.rake
+++ b/lib/tasks/foodsoft_setup.rake
@@ -1,3 +1,5 @@
+require 'stringio'
+
# put in here all foodsoft tasks
# => :environment loads the environment an gives easy access to the application
@@ -23,8 +25,6 @@ module Colors
end
include Colors
-require 'stringio'
-
namespace :foodsoft do
desc "Setup foodsoft"
task :setup_development do
@@ -82,10 +82,11 @@ end
def setup_app_config
file = 'config/app_config.yml'
+ sample = Rails.root.join("#{file}.SAMPLE")
return nil if skip?(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)
end
@@ -120,6 +121,8 @@ def start_server
puts blue "Start your server running 'bundle exec rails s' and visit http://localhost:3000"
end
+# Helper Methods
+
def ask(question, answers = false)
puts question
input = STDIN.gets.chomp
@@ -149,3 +152,4 @@ def capture_stdout
ensure
$stdout = STDOUT
end
+
diff --git a/lib/token_verifier.rb b/lib/token_verifier.rb
new file mode 100644
index 00000000..248a3ee8
--- /dev/null
+++ b/lib/token_verifier.rb
@@ -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
diff --git a/spec/lib/token_verifier_spec.rb b/spec/lib/token_verifier_spec.rb
new file mode 100644
index 00000000..d398a140
--- /dev/null
+++ b/spec/lib/token_verifier_spec.rb
@@ -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