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 9c77fe48..95d27b00 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 9d6005bc..54d2c82f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -271,6 +271,7 @@ en:
error_authn: Authentication required!
error_denied: Access denied!
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 552791f3..704db941 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
@@ -57,4 +61,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..84407363 100644
--- a/lib/foodsoft_wiki/lib/foodsoft_wiki.rb
+++ b/lib/foodsoft_wiki/lib/foodsoft_wiki.rb
@@ -1,5 +1,6 @@
require 'wikicloth'
require 'acts_as_versioned'
+require "diffy"
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