diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Gemfile | 2 | ||||
-rw-r--r-- | Gemfile.lock | 6 | ||||
-rw-r--r-- | app/controllers/track_controller.rb | 3 | ||||
-rw-r--r-- | app/models/public_body.rb | 2 | ||||
m--------- | commonlib | 0 | ||||
-rw-r--r-- | config/initializers/alaveteli.rb | 2 | ||||
-rw-r--r-- | config/initializers/theme_loader.rb | 3 | ||||
-rw-r--r-- | lib/actionmailer_patches.rb | 15 | ||||
-rw-r--r-- | lib/tasks/import.rake | 78 | ||||
-rw-r--r-- | lib/tasks/stats.rake | 5 | ||||
-rw-r--r-- | lib/tasks/themes.rake | 2 | ||||
-rw-r--r-- | lib/theme.rb | 3 | ||||
-rwxr-xr-x | script/switch-theme.rb | 120 | ||||
-rw-r--r-- | spec/controllers/track_controller_spec.rb | 33 | ||||
-rw-r--r-- | spec/factories.rb | 7 | ||||
-rw-r--r-- | spec/fixtures/files/fake-authority-type.csv | 2 | ||||
-rw-r--r-- | spec/lib/theme_spec.rb | 25 | ||||
-rw-r--r-- | spec/models/public_body_spec.rb | 25 |
19 files changed, 313 insertions, 21 deletions
diff --git a/.gitignore b/.gitignore index f328f6705..d2c256ef1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ TAGS bin/ config/aliases config/httpd.conf +config/general*.yml .sass-cache alaveteli.sublime* webrat.log @@ -49,7 +49,7 @@ gem 'globalize3', :git => 'git://github.com/henare/globalize3.git', :branch => ' gem 'locale' gem 'routing-filter' gem 'unicode' -gem 'unidecode' +gem 'unidecoder' group :test do gem 'fakeweb' diff --git a/Gemfile.lock b/Gemfile.lock index 4494c2342..9accf0283 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,7 +136,7 @@ GEM net-ssh (2.6.7) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) - newrelic_rpm (3.6.2.96) + newrelic_rpm (3.6.8.164) nokogiri (1.5.9) paper_trail (2.7.2) activerecord (~> 3.0) @@ -236,7 +236,7 @@ GEM polyglot (>= 0.3.1) tzinfo (0.3.37) unicode (0.4.4) - unidecode (1.0.0) + unidecoder (1.1.2) vpim (0.695) webrat (0.7.3) nokogiri (>= 1.2.0) @@ -293,7 +293,7 @@ DEPENDENCIES statistics2 (~> 0.54) syslog_protocol unicode - unidecode + unidecoder vpim webrat will_paginate diff --git a/app/controllers/track_controller.rb b/app/controllers/track_controller.rb index 40fa69290..72c092221 100644 --- a/app/controllers/track_controller.rb +++ b/app/controllers/track_controller.rb @@ -181,7 +181,8 @@ class TrackController < ApplicationController if new_medium == 'delete' track_thing.destroy flash[:notice] = _("You are no longer following {{track_description}}.", :track_description => track_thing.params[:list_description]) - redirect_to params[:r] + redirect_to URI.parse(params[:r]).path + # Reuse code like this if we let medium change again. #elsif new_medium == 'email_daily' # track_thing.track_medium = new_medium diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 485a794b0..9adcdc4a0 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -407,6 +407,8 @@ class PublicBody < ActiveRecord::Base fields = {} field_names.each{|name, i| fields[name] = row[i]} + yield line, fields if block_given? + name = row[field_names['name']] email = row[field_names['request_email']] next if name.nil? diff --git a/commonlib b/commonlib -Subproject 9462a28fe12b25637d6e67d7140d444632e3ff7 +Subproject 77a6b09daa5da3808be4431799521f8bee5ab21 diff --git a/config/initializers/alaveteli.rb b/config/initializers/alaveteli.rb index 8ae78c80c..5171c052f 100644 --- a/config/initializers/alaveteli.rb +++ b/config/initializers/alaveteli.rb @@ -50,6 +50,8 @@ require 'normalize_string' require 'alaveteli_file_types' require 'alaveteli_localization' require 'message_prominence' +require 'actionmailer_patches' +require 'theme' AlaveteliLocalization.set_locales(AlaveteliConfiguration::available_locales, AlaveteliConfiguration::default_locale) diff --git a/config/initializers/theme_loader.rb b/config/initializers/theme_loader.rb index 1ad2d01f1..b3ae11e1e 100644 --- a/config/initializers/theme_loader.rb +++ b/config/initializers/theme_loader.rb @@ -18,7 +18,6 @@ if Rails.env == "test" end else for url in AlaveteliConfiguration::theme_urls.reverse - theme_name = url.sub(/.*\/(.*).git/, "\\1") - require_theme(theme_name) + require_theme theme_url_to_theme_name(url) end end diff --git a/lib/actionmailer_patches.rb b/lib/actionmailer_patches.rb new file mode 100644 index 000000000..600d3c8cc --- /dev/null +++ b/lib/actionmailer_patches.rb @@ -0,0 +1,15 @@ +# Monkey patch for CVE-2013-4389 +# derived from http://seclists.org/oss-sec/2013/q4/118 to fix +# a possible DoS vulnerability in the log subscriber component of +# Action Mailer. + +require 'action_mailer' +module ActionMailer + class LogSubscriber < ActiveSupport::LogSubscriber + def deliver(event) + recipients = Array.wrap(event.payload[:to]).join(', ') + info("\nSent mail to #{recipients} (#{event.duration.round(1)}ms)") + debug(event.payload[:mail]) + end + end +end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake new file mode 100644 index 000000000..0e8397fde --- /dev/null +++ b/lib/tasks/import.rake @@ -0,0 +1,78 @@ +require 'csv' +require 'tempfile' + +namespace :import do + + desc 'Import public bodies from CSV provided on standard input' + task :import_csv => :environment do + dryrun = ENV['DRYRUN'] != '0' + if dryrun + STDERR.puts "Only a dry run; public bodies will not be created" + end + + tmp_csv = nil + Tempfile.open('alaveteli') do |f| + f.write STDIN.read + tmp_csv = f + end + + number_of_rows = 0 + + STDERR.puts "Preliminary check for ambiguous names or slugs..." + + # Check that the name and slugified version of the name are + # unique: + url_part_count = Hash.new { 0 } + name_count = Hash.new { 0 } + reader = CSV.open tmp_csv.path, 'r' + header_line = reader.shift + headers = header_line.collect { |h| h.gsub /^#/, ''} + + reader.each do |row_array| + row = Hash[headers.zip row_array] + name = row['name'] + url_part = MySociety::Format::simplify_url_part name, "body" + name_count[name] += 1 + url_part_count[url_part] += 1 + number_of_rows += 1 + end + + non_unique_error = false + + [[name_count, 'name'], + [url_part_count, 'url_part']].each do |counter, field| + counter.sort.map do |name, count| + if count > 1 + non_unique_error = true + STDERR.puts "The #{field} #{name} was found #{count} times." + end + end + end + + next if non_unique_error + + STDERR.puts "Now importing the public bodies..." + + # Now it's (probably) safe to try to import: + errors, notes = PublicBody.import_csv(tmp_csv.path, + tag='', + tag_behaviour='replace', + dryrun, + editor="#{ENV['USER']} (Unix user)", + I18n.available_locales) do |row_number, fields| + percent_complete = (100 * row_number.to_f / number_of_rows).to_i + STDERR.print "#{row_number} out of #{number_of_rows} " + STDERR.puts "(#{percent_complete}% complete)" + end + + if errors.length > 0 + STDERR.puts "Import failed, with the following errors:" + errors.each do |error| + STDERR.puts " #{error}" + end + else + STDERR.puts "Done." + end + + end +end diff --git a/lib/tasks/stats.rake b/lib/tasks/stats.rake index 4eda27289..eb36204c6 100644 --- a/lib/tasks/stats.rake +++ b/lib/tasks/stats.rake @@ -94,7 +94,7 @@ namespace :stats do desc 'Update statistics in the public_bodies table' task :update_public_bodies_stats => :environment do verbose = ENV['VERBOSE'] == '1' - PublicBody.all.each do |public_body| + PublicBody.find_each(:batch_size => 10) do |public_body| puts "Counting overdue requests for #{public_body.name}" if verbose # Look for values of 'waiting_response_overdue' and @@ -102,7 +102,8 @@ namespace :stats do # described_state column, and instead need to be calculated: overdue_count = 0 very_overdue_count = 0 - InfoRequest.find_each(:conditions => {:public_body_id => public_body.id}) do |ir| + InfoRequest.find_each(:batch_size => 200, + :conditions => {:public_body_id => public_body.id}) do |ir| case ir.calculate_status when 'waiting_response_very_overdue' very_overdue_count += 1 diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake index a8d16f108..1eed92f1e 100644 --- a/lib/tasks/themes.rake +++ b/lib/tasks/themes.rake @@ -85,7 +85,7 @@ namespace :themes do def install_theme(theme_url, verbose, deprecated=false) deprecation_string = deprecated ? " using deprecated THEME_URL" : "" - theme_name = File.basename(theme_url, '.git') + theme_name = theme_url_to_theme_name theme_url puts "Installing theme #{theme_name}#{deprecation_string} from #{theme_url}" uninstall(theme_name, verbose) if installed?(theme_name) install_theme_using_git(theme_name, theme_url, verbose) diff --git a/lib/theme.rb b/lib/theme.rb new file mode 100644 index 000000000..4f03b5d99 --- /dev/null +++ b/lib/theme.rb @@ -0,0 +1,3 @@ +def theme_url_to_theme_name(theme_url) + File.basename theme_url, '.git' +end diff --git a/script/switch-theme.rb b/script/switch-theme.rb new file mode 100755 index 000000000..47f81c7a8 --- /dev/null +++ b/script/switch-theme.rb @@ -0,0 +1,120 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- + +# A simple script to swap around your Alaveteli themes when you're +# hacking on Alaveteli. By default this assumes that you have an +# 'alaveteli-themes' directory at the same level as your alaveteli git +# repository, e.g.: +# +# alaveteli +# ├── app +# ├── cache +# ... +# └── vendor +# alaveteli-themes/ +# ├── alavetelitheme +# ├── asktheeu-theme +# ├── chiediamo-theme +# ├── ipvtheme +# ├── queremossabertheme +# ├── tuderechoasaber-theme +# ├── whatdotheyknow-theme +# └── yourrighttoknow +# +# However, you can override the location of your themes directory with +# the environment variable ALAVETELI_THEMES_DIR. +# +# You need to have a corresponding general.yml file called, for example: +# +# config/general-whatdotheyknow-theme.yml +# config/general-yourrighttoknow.yml + +require 'tempfile' + +theme_directory = ENV['ALAVETELI_THEMES_DIR'] +alaveteli_directory = File.expand_path(File.join(File.dirname(__FILE__), + "..")) +unless theme_directory + theme_directory = File.expand_path File.join(alaveteli_directory, + '..', + 'alaveteli-themes') +end + +unless File.exists? theme_directory + STDERR.puts "The theme directory '#{theme_directory}' didn't exist." + exit 1 +end + +# Assume that any directory directly under theme_directory is a theme: +$available_themes = Dir.entries(theme_directory).find_all do |local_theme_name| + next if [".", ".."].index local_theme_name + next unless local_theme_name + full_path = File.join theme_directory, local_theme_name + next unless File.directory? full_path + next unless File.directory? File.join(full_path, '.git') + local_theme_name +end + +if $available_themes.empty? + STDERR.puts "There were no theme directories found in '#{theme_directory}'" + exit +end + +def usage_and_exit + STDERR.puts "Usage: #{$0} <THEME-NAME>" + $available_themes.sort.each do |theme_name| + STDERR.puts " #{theme_name}" + end + exit 1 +end + +usage_and_exit unless ARGV.length == 1 +requested_theme = ARGV[0] +usage_and_exit unless $available_themes.include? requested_theme + +full_theme_path = File.join theme_directory, requested_theme + +config_directory = File.join alaveteli_directory, 'config' +general_filename = File.join config_directory, "general.yml" +theme_filename = File.join config_directory, "general-#{requested_theme}.yml" + +if File.exists?(general_filename) && ! (File.symlink? general_filename) + STDERR.puts "'#{general_filename}' exists, but isn't a symlink" + exit 1 +end + +unless File.exists? theme_filename + STDERR.puts "'#{theme_filename}' didn't exist" + exit 1 +end + +def symlink target, link_directory, link_name + tmp = Tempfile.new link_name, link_directory + if system("ln", "-sfn", target, tmp.path) + full_link_name = File.join(link_directory, link_name) + begin + File.rename tmp.path, full_link_name + rescue Errno::EISDIR + STDERR.puts "Couldn't overwrite #{full_link_name} since it's a directory" + exit 1 + end + else + STDERR.puts "Failed to create a symlink from #{tmp.path} to #{target}" + exit 1 + end +end + +symlink(File.basename(theme_filename), + config_directory, + "general.yml") + +symlink(File.join(full_theme_path, 'public'), + File.join(alaveteli_directory, 'public'), + 'alavetelitheme') + +symlink(full_theme_path, + File.join(alaveteli_directory, 'vendor', 'plugins'), + requested_theme) + +STDERR.puts """Switched to #{requested_theme}! +You will need to restart any development server you have running.""" diff --git a/spec/controllers/track_controller_spec.rb b/spec/controllers/track_controller_spec.rb index a16024828..57d084f6b 100644 --- a/spec/controllers/track_controller_spec.rb +++ b/spec/controllers/track_controller_spec.rb @@ -55,6 +55,39 @@ describe TrackController, "when making a new track on a request" do end +describe TrackController, "when unsubscribing from a track" do + + before do + @track_thing = FactoryGirl.create(:track_thing) + end + + it 'should destroy the track thing' do + get :update, {:track_id => @track_thing.id, + :track_medium => 'delete', + :r => 'http://example.com'}, + {:user_id => @track_thing.tracking_user.id} + TrackThing.find(:first, :conditions => ['id = ? ', @track_thing.id]).should == nil + end + + it 'should redirect to a URL on the site' do + get :update, {:track_id => @track_thing.id, + :track_medium => 'delete', + :r => '/'}, + {:user_id => @track_thing.tracking_user.id} + response.should redirect_to('/') + end + + it 'should not redirect to a url on another site' do + track_thing = FactoryGirl.create(:track_thing) + get :update, {:track_id => @track_thing.id, + :track_medium => 'delete', + :r => 'http://example.com/'}, + {:user_id => @track_thing.tracking_user.id} + response.should redirect_to('/') + end + +end + describe TrackController, "when sending alerts for a track" do render_views diff --git a/spec/factories.rb b/spec/factories.rb index 653525920..7d8f94ac1 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -137,4 +137,11 @@ FactoryGirl.define do last_edit_comment "Making an edit" end + factory :track_thing do + association :tracking_user, :factory => :user + track_medium 'email_daily' + track_type 'search_query' + track_query 'Example Query' + end + end diff --git a/spec/fixtures/files/fake-authority-type.csv b/spec/fixtures/files/fake-authority-type.csv index cb25050c6..a320941c7 100644 --- a/spec/fixtures/files/fake-authority-type.csv +++ b/spec/fixtures/files/fake-authority-type.csv @@ -2,3 +2,5 @@ ,Scottish Fake Authority,scottish_foi@localhost ,Fake Authority of Northern Ireland,ni_foi@localhost ,Gobierno de Aragón,spain_foi@localhost +,Nordic æøå,no_foi@localhost + diff --git a/spec/lib/theme_spec.rb b/spec/lib/theme_spec.rb new file mode 100644 index 000000000..829c1a269 --- /dev/null +++ b/spec/lib/theme_spec.rb @@ -0,0 +1,25 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "theme_url_to_theme_name" do + + it "should deal with a typical bare repo URL" do + url = 'git://wherever/blah-theme.git' + theme_url_to_theme_name(url).should == 'blah-theme' + end + + it "should deal with a typical bare repo URL with trailing slashes" do + url = 'ssh://wherever/blah-theme.git//' + theme_url_to_theme_name(url).should == 'blah-theme' + end + + it "should deal with a typical non-bare repo URL" do + url = '/home/whoever/themes/blah-theme' + theme_url_to_theme_name(url).should == 'blah-theme' + end + + it "should deal with a typical non-bare repo URL with a trailing slash" do + url = '/home/whoever/themes/blah-theme/' + theme_url_to_theme_name(url).should == 'blah-theme' + end + +end diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb index 0324e3f5a..7a2c60722 100644 --- a/spec/models/public_body_spec.rb +++ b/spec/models/public_body_spec.rb @@ -320,14 +320,15 @@ describe PublicBody, " when loading CSV files" do csv_contents = normalize_string_to_utf8(load_file_fixture("fake-authority-type.csv")) errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', true, 'someadmin') # true means dry run errors.should == [] - notes.size.should == 5 - notes[0..3].should == [ + notes.size.should == 6 + notes[0..4].should == [ "line 1: creating new authority 'North West Fake Authority' (locale: en):\n\t\{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\"\}", "line 2: creating new authority 'Scottish Fake Authority' (locale: en):\n\t\{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\"\}", "line 3: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t\{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\"\}", "line 4: creating new authority 'Gobierno de Aragón' (locale: en):\n\t\{\"name\":\"Gobierno de Arag\\u00f3n\",\"request_email\":\"spain_foi@localhost\"}", + "line 5: creating new authority 'Nordic æøå' (locale: en):\n\t{\"name\":\"Nordic \\u00e6\\u00f8\\u00e5\",\"request_email\":\"no_foi@localhost\"}" ] - notes[4].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( .+\n)*You may want to delete them manually.\n/ + notes[5].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( .+\n)*You may want to delete them manually.\n/ PublicBody.count.should == original_count end @@ -338,16 +339,17 @@ describe PublicBody, " when loading CSV files" do csv_contents = normalize_string_to_utf8(load_file_fixture("fake-authority-type.csv")) errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', false, 'someadmin') # false means real run errors.should == [] - notes.size.should == 5 - notes[0..3].should == [ + notes.size.should == 6 + notes[0..4].should == [ "line 1: creating new authority 'North West Fake Authority' (locale: en):\n\t\{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\"\}", "line 2: creating new authority 'Scottish Fake Authority' (locale: en):\n\t\{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\"\}", "line 3: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t\{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\"\}", "line 4: creating new authority 'Gobierno de Aragón' (locale: en):\n\t\{\"name\":\"Gobierno de Arag\\u00f3n\",\"request_email\":\"spain_foi@localhost\"}", + "line 5: creating new authority 'Nordic æøå' (locale: en):\n\t{\"name\":\"Nordic \\u00e6\\u00f8\\u00e5\",\"request_email\":\"no_foi@localhost\"}" ] - notes[4].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( .+\n)*You may want to delete them manually.\n/ + notes[5].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( .+\n)*You may want to delete them manually.\n/ - PublicBody.count.should == original_count + 4 + PublicBody.count.should == original_count + 5 end it "should do imports without a tag successfully" do @@ -356,15 +358,16 @@ describe PublicBody, " when loading CSV files" do csv_contents = normalize_string_to_utf8(load_file_fixture("fake-authority-type.csv")) errors, notes = PublicBody.import_csv(csv_contents, '', 'replace', false, 'someadmin') # false means real run errors.should == [] - notes.size.should == 5 - notes[0..3].should == [ + notes.size.should == 6 + notes[0..4].should == [ "line 1: creating new authority 'North West Fake Authority' (locale: en):\n\t\{\"name\":\"North West Fake Authority\",\"request_email\":\"north_west_foi@localhost\"\}", "line 2: creating new authority 'Scottish Fake Authority' (locale: en):\n\t\{\"name\":\"Scottish Fake Authority\",\"request_email\":\"scottish_foi@localhost\"\}", "line 3: creating new authority 'Fake Authority of Northern Ireland' (locale: en):\n\t\{\"name\":\"Fake Authority of Northern Ireland\",\"request_email\":\"ni_foi@localhost\"\}", "line 4: creating new authority 'Gobierno de Aragón' (locale: en):\n\t\{\"name\":\"Gobierno de Arag\\u00f3n\",\"request_email\":\"spain_foi@localhost\"}", + "line 5: creating new authority 'Nordic æøå' (locale: en):\n\t{\"name\":\"Nordic \\u00e6\\u00f8\\u00e5\",\"request_email\":\"no_foi@localhost\"}" ] - notes[4].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( .+\n)*You may want to delete them manually.\n/ - PublicBody.count.should == original_count + 4 + notes[5].should =~ /Notes: Some bodies are in database, but not in CSV file:\n( .+\n)*You may want to delete them manually.\n/ + PublicBody.count.should == original_count + 5 end it "should handle a field list and fields out of order" do |