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