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