diff options
Diffstat (limited to 'app/controllers')
-rw-r--r-- | app/controllers/admin_controller.rb | 16 | ||||
-rw-r--r-- | app/controllers/admin_public_body_controller.rb | 28 | ||||
-rw-r--r-- | app/controllers/admin_request_controller.rb | 4 | ||||
-rw-r--r-- | app/controllers/application_controller.rb | 52 | ||||
-rw-r--r-- | app/controllers/comment_controller.rb | 1 | ||||
-rw-r--r-- | app/controllers/general_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/public_body_controller.rb | 48 | ||||
-rw-r--r-- | app/controllers/request_controller.rb | 87 | ||||
-rw-r--r-- | app/controllers/user_controller.rb | 99 |
9 files changed, 252 insertions, 85 deletions
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 7f8cfbd67..ca5538e03 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -19,19 +19,9 @@ class AdminController < ApplicationController # Expire cached attachment files for a request def expire_for_request(info_request) - # Clear out cached entries - use low level disk removal, even though we - # are clearing results from caches_action, for several reasons: - # * We can't use expire_action here, as it doesn't seem to be - # compatible with the :only_path we used in the caches_action - # call. - # * Removing everything is simpler than having to get all the - # parameters right for the path, and calling for HTML version vs. raw - # attachment version. - # * We cope properly with filenames changed by censor rules, which - # change the URL. - # * We could use expire_fragment with a Regexp, but it walks the whole - # cache which is insanely slow - cache_subpath = File.join(self.cache_store.cache_path, "views/request/#{info_request.id}") + # Clear out cached entries, by removing files from disk (the built in + # Rails fragment cache made doing this and other things too hard) + cache_subpath = foi_fragment_cache_all_for_request(info_request) FileUtils.rm_rf(cache_subpath) # Remove the database caches of body / attachment text (the attachment text diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index 74a3a86c6..bce04ff98 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -120,7 +120,15 @@ class AdminPublicBodyController < AdminController def import_csv if params[:csv_file] - if not params[:tag].empty? + if !params[:tag].empty? + if params['commit'] == 'Dry run' + dry_run_only = true + elsif params['commit'] == 'Upload' + dry_run_only = false + else + raise "internal error, unknown button label" + end + # Try with dry run first csv_contents = params[:csv_file].read en = PublicBody.import_csv(csv_contents, params[:tag], true, admin_http_auth_user()) @@ -128,14 +136,18 @@ class AdminPublicBodyController < AdminController notes = en[1] if errors.size == 0 - # And if OK, with real run - en = PublicBody.import_csv(csv_contents, params[:tag], false, admin_http_auth_user()) - errors = en[0] - notes = en[1] - if errors.size != 0 - raise "dry run mismatched real run" + if dry_run_only + notes.push("Dry run was successful, real run would do as above.") + else + # And if OK, with real run + en = PublicBody.import_csv(csv_contents, params[:tag], false, admin_http_auth_user()) + errors = en[0] + notes = en[1] + if errors.size != 0 + raise "dry run mismatched real run" + end + notes.push("Import was successful.") end - notes.push("Import was successful.") end @errors = errors.join("\n") @notes = notes.join("\n") diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index ff2772b0e..f077691ff 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -215,10 +215,10 @@ class AdminRequestController < AdminController # Bejeeps, look, sometimes a URL is something that belongs in a controller, jesus. # XXX hammer this square peg into the round MVC hole - should be calling main_url(upload_response_url()) post_redirect = PostRedirect.new( - :uri => upload_response_url(:url_title => info_request.url_title), + :uri => main_url(upload_response_url(:url_title => info_request.url_title, :only_path => true)), :user_id => user.id) post_redirect.save! - url = confirm_url(:email_token => post_redirect.email_token) + url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true)) flash[:notice] = 'Send "' + name + '" <<a href="mailto:' + email + '">' + email + '</a>> this URL: <a href="' + url + '">' + url + "</a> - it will log them in and let them upload a response to this request." redirect_to request_admin_url(info_request) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5055519ec..9ee1c250b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -40,21 +40,12 @@ class ApplicationController < ActionController::Base before_filter :session_remember_me def session_remember_me # Reset the "sliding window" session expiry time. - if session[:remember_me] - expire_time = 1.month.from_now - # "Why is session[:force_new_cookie] set to Time.now? In order for the “sliding window” - # concept to work, a fresh cookie must be sent with every response. Rails only - # sends a cookie when the session data has changed so using a value like Time.now - # ensures that it changes every time. What I have actually found is that some - # internal voodoo causes the session data to change slightly anyway but it’s best - # to be sure!" - session[:force_new_cookie] = Time.now - else - expire_time = nil - end - # if statement here is so test code runs - if session.instance_variable_get(:@dbman) - session.instance_variable_get(:@dbman).instance_variable_get(:@cookie_options)['expires'] = expire_time + if request.env['rack.session.options'] + if session[:remember_me] + request.env['rack.session.options'][:expire_after] = 1.month + else + request.env['rack.session.options'][:expire_after] = nil + end end end @@ -100,6 +91,27 @@ class ApplicationController < ActionController::Base controller_example_group.get params[:action], params end + # Used to work out where to cache fragments. We add an extra path to the + # URL using the first three digits of the info request id, because we can't + # have more than 32,000 entries in one directory on an ext3 filesystem. + def foi_fragment_cache_part_path(param) + path = url_for(param) + id = param['id'] || param[:id] + first_three_digits = id.to_s()[0..2] + path = path.sub("/request/", "/request/" + first_three_digits + "/") + return path + end + def foi_fragment_cache_path(param) + path = foi_fragment_cache_part_path(param) + path = "/views" + path + return File.join(self.cache_store.cache_path, path) + end + def foi_fragment_cache_all_for_request(info_request) + first_three_digits = info_request.id.to_s()[0..2] + path = "views/request/#{first_three_digits}/#{info_request.id}" + return File.join(self.cache_store.cache_path, path) + end + private # Check the user is logged in @@ -184,6 +196,16 @@ class ApplicationController < ActionController::Base end end + # + def check_read_only + read_only = MySociety::Config.get('READ_ONLY') + if !read_only.empty? + flash[:notice] = "<p>WhatDoTheyKnow is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.</p> <p>" + read_only + "</p>" + redirect_to frontpage_url + end + + end + # For administration interface, return display name of authenticated user def admin_http_auth_user # This needs special magic in mongrel: http://www.ruby-forum.com/topic/83067 diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb index dfa31f9ef..d5f8f89fb 100644 --- a/app/controllers/comment_controller.rb +++ b/app/controllers/comment_controller.rb @@ -7,6 +7,7 @@ # $Id: comment_controller.rb,v 1.9 2009-03-09 01:17:04 francis Exp $ class CommentController < ApplicationController + before_filter :check_read_only, :only => [ :new ] def new if params[:type] == 'request' diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb index 5c4103616..efab26fad 100644 --- a/app/controllers/general_controller.rb +++ b/app/controllers/general_controller.rb @@ -7,7 +7,7 @@ # # $Id: general_controller.rb,v 1.57 2009-10-03 10:23:43 francis Exp $ -require 'xmlsimple' +require 'lib/xmlsimple' require 'open-uri' class GeneralController < ApplicationController diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 1ca82e463..591081fe9 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -6,6 +6,8 @@ # # $Id: public_body_controller.rb,v 1.8 2009-09-14 13:27:00 francis Exp $ +require 'csv' + class PublicBodyController < ApplicationController # XXX tidy this up with better error messages, and a more standard infrastructure for the redirect to canonical URL def show @@ -93,5 +95,51 @@ class PublicBodyController < ApplicationController cache_in_squid end + + # Used so URLs like /local/islington work, for use e.g. writing to a local paper. + def list_redirect + @tag = params[:tag] + redirect_to list_public_bodies_url(:tag => @tag) + end + + def list_all_csv + public_bodies = PublicBody.find(:all, :order => 'url_name') + report = StringIO.new + CSV::Writer.generate(report, ',') do |title| + title << [ + 'Name', + 'Short name', + # deliberately not including 'Request email' + 'URL name', + 'Tags', + 'Home page', + 'Publication scheme', + 'Charity number', + 'Created at', + 'Updated at', + 'Version', + ] + public_bodies.each do |public_body| + title << [ + public_body.name, + public_body.short_name, + # DO NOT include request_email (we don't want to make it + # easy to spam all authorities with requests) + public_body.url_name, + public_body.tag_string, + public_body.calculated_home_page, + public_body.publication_scheme, + public_body.charity_number, + public_body.created_at, + public_body.updated_at, + public_body.version, + ] + end + end + report.rewind + send_data(report.read, :type=> 'text/csv; charset=utf-8; header=present', + :filename => 'all-authorities.csv', + :disposition =>'attachment', :encoding => 'utf8') + end end diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 2606a9609..0664093c3 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -7,6 +7,7 @@ # $Id: request_controller.rb,v 1.192 2009-10-19 19:26:40 francis Exp $ class RequestController < ApplicationController + before_filter :check_read_only, :only => [ :new, :show_response, :describe_state ] def show # Look up by old style numeric identifiers @@ -64,11 +65,26 @@ class RequestController < ApplicationController @last_response = @info_request.get_last_response end + # Extra info about a request, such as event history + def details + @info_request = InfoRequest.find_by_url_title(params[:url_title]) + if !@info_request.user_can_view?(authenticated_user) + render :template => 'request/hidden', :status => 410 # gone + return + end + + @columns = ['id', 'event_type', 'created_at', 'described_state', 'last_described_at', 'calculated_state' ] + end + # Requests similar to this one def similar @per_page = 25 @page = (params[:page] || "1").to_i @info_request = InfoRequest.find_by_url_title(params[:url_title]) + if !@info_request.user_can_view?(authenticated_user) + render :template => 'request/hidden', :status => 410 # gone + return + end @xapian_object = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events, :offset => (@page - 1) * @per_page, :limit => @per_page, :collapse_by_prefix => 'request_collapse') @@ -148,27 +164,27 @@ class RequestController < ApplicationController # First time we get to the page, just display it if params[:submitted_new_request].nil? || params[:reedit] # Read parameters in - public body must be passed in - if params[:public_body_id] - params[:info_request] = { :public_body_id => params[:public_body_id] } + params[:info_request] = { :public_body_id => params[:public_body_id] } if !params[:info_request] + if !params[:info_request][:public_body_id] + redirect_to frontpage_url + return end @info_request = InfoRequest.new(params[:info_request]) params[:info_request_id] = @info_request.id + params[:outgoing_message] = {} if !params[:outgoing_message] + params[:outgoing_message][:info_request] = @info_request @outgoing_message = OutgoingMessage.new(params[:outgoing_message]) @outgoing_message.set_signature_name(@user.name) if !@user.nil? - if @info_request.public_body.nil? - redirect_to frontpage_url - else - if @info_request.public_body.is_requestable? - render :action => 'new' + if @info_request.public_body.is_requestable? + render :action => 'new' + else + if @info_request.public_body.not_requestable_reason == 'bad_contact' + render :action => 'new_bad_contact' else - if @info_request.public_body.not_requestable_reason == 'bad_contact' - render :action => 'new_bad_contact' - else - # if not requestable because defunct or not_apply, redirect to main page - # (which doesn't link to the /new/ URL) - redirect_to public_body_url(@info_request.public_body) - end + # if not requestable because defunct or not_apply, redirect to main page + # (which doesn't link to the /new/ URL) + redirect_to public_body_url(@info_request.public_body) end end return @@ -318,11 +334,14 @@ class RequestController < ApplicationController # Display advice for requester on what to do next, as appropriate if @info_request.calculate_status == 'waiting_response' - flash[:notice] = "<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>" + flash[:notice] = "<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and " + (@info_request.public_body.is_school? ? "in term time" : "") + " normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>" redirect_to request_url(@info_request) elsif @info_request.calculate_status == 'waiting_response_overdue' - flash[:notice] = "<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>" + flash[:notice] = "<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and " + (@info_request.public_body.is_school? ? "in term time" : "") + " normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>" redirect_to request_url(@info_request) + elsif @info_request.calculate_status == 'waiting_response_very_overdue' + flash[:notice] = "<p>Thank you! Your request is long overdue, by more than 40 working days. Most requests should be answered within 20 working days. You might like to complain about this, see below.</p>" + redirect_to unhappy_url(@info_request) elsif @info_request.calculate_status == 'not_held' flash[:notice] = "<p>Thank you! Here are some ideas on what to do next:</p> <ul> @@ -337,7 +356,7 @@ class RequestController < ApplicationController " redirect_to request_url(@info_request) elsif @info_request.calculate_status == 'rejected' - flash[:notice] = "Oh no! Sorry to hear that your request was rejected. Here is what to do now." + flash[:notice] = "Oh no! Sorry to hear that your request was refused. Here is what to do now." redirect_to unhappy_url(@info_request) elsif @info_request.calculate_status == 'successful' flash[:notice] = "<p>We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.</p><p>If you found WhatDoTheyKnow useful, <a href=\"http://www.mysociety.org/donate/\">make a donation</a> to the charity which runs it.</p>" @@ -360,8 +379,8 @@ class RequestController < ApplicationController flash[:notice] = "Please use the form below to tell us more." redirect_to help_general_url(:action => 'contact') elsif @info_request.calculate_status == 'user_withdrawn' - flash[:notice] = "Thanks for letting us know that you've withdrawn your request. Please add an annotation below to let other people know why you withdrew it." - redirect_to request_url(@info_request) + flash[:notice] = "If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn." + redirect_to respond_to_last_url(@info_request) else raise "unknown calculate_status " + @info_request.calculate_status end @@ -519,7 +538,7 @@ class RequestController < ApplicationController # Test for hidden incoming_message = IncomingMessage.find(params[:incoming_message_id]) if !incoming_message.info_request.user_can_view?(authenticated_user) - render :template => 'request/hidden' + render :template => 'request/hidden', :status => 410 # gone end end @@ -527,8 +546,10 @@ class RequestController < ApplicationController around_filter :cache_attachments, :only => [ :get_attachment, :get_attachment_as_html ] def cache_attachments key = params.merge(:only_path => true) - if cached = read_fragment(key) - #if cached = 'zzz***zzz' + key_path = foi_fragment_cache_path(key) + + if File.exists?(key_path) + cached = File.read(key_path) IncomingMessage # load global filename_to_mimetype XXX should move filename_to_mimetype to proper namespace response.content_type = filename_to_mimetype(params[:file_name].join("/")) or 'application/octet-stream' render_for_text(cached) @@ -537,7 +558,14 @@ class RequestController < ApplicationController yield - write_fragment(key, response.body) + # write it to the fileystem ourselves, so is just a plain file. (The + # various fragment cache functions using Ruby Marshall to write the file + # which adds a header, so isnt compatible with images that have been + # extracted elsewhere from PDFs) + FileUtils.mkdir_p(File.dirname(key_path)) + File.atomic_write(key_path) do |f| + f.write(response.body) + end end def get_attachment @@ -558,14 +586,16 @@ class RequestController < ApplicationController # images made during conversion (e.g. images in PDF files) are put in the cache directory, so # the same cache code in cache_attachments above will display them. - image_dir = File.dirname(ActionController::Base.cache_store.cache_path + "/views" + url_for(params.merge(:only_path => true))) + key = params.merge(:only_path => true) + key_path = foi_fragment_cache_path(key) + image_dir = File.dirname(key_path) FileUtils.mkdir_p(image_dir) - html = @attachment.body_as_html(image_dir) + html, wrapper_id = @attachment.body_as_html(image_dir) view_html_stylesheet = render_to_string :partial => "request/view_html_stylesheet" html.sub!(/<head>/i, "<head>" + view_html_stylesheet) - html.sub!(/<body[^>]*>/i, '<body><prefix-here><div id="wrapper"><div id="view_html_content">' + view_html_stylesheet) - html.sub!(/<\/body[^>]*>/i, '</div></div></body>' + view_html_stylesheet) + html.sub!(/<body[^>]*>/i, '<body><prefix-here><div id="' + wrapper_id + '"><div id="view_html_content">') + html.sub!(/<\/body[^>]*>/i, '</div></div></body>') view_html_prefix = render_to_string :partial => "request/view_html_prefix" html.sub!("<prefix-here>", view_html_prefix) @@ -595,6 +625,7 @@ class RequestController < ApplicationController raise "internal error, pre-auth filter should have caught this" if !@info_request.user_can_view?(authenticated_user) @attachment = IncomingMessage.get_attachment_by_url_part_number(@incoming_message.get_attachments_for_display, @part_number) + raise "attachment not found part number " + @part_number.to_s + " incoming_message " + @incoming_message.id.to_s if @attachment.nil? # check filename in URL matches that in database (use a censor rule if you want to change a filename) raise "please use same filename as original file has, display: '" + @attachment.display_filename + "' old_display: '" + @attachment.old_display_filename + "' original: '" + @original_filename + "'" if @attachment.display_filename != @original_filename && @attachment.old_display_filename != @original_filename @@ -631,7 +662,7 @@ class RequestController < ApplicationController if params[:submitted_upload_response] file_name = nil file_content = nil - if params[:file_1].class.to_s == "ActionController::UploadedTempfile" + if !params[:file_1].nil? file_name = params[:file_1].original_filename file_content = params[:file_1].read end diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index bc117ce2e..b3f9511b5 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -147,10 +147,13 @@ class UserController < ApplicationController end # Logout form - def signout + def _do_signout session[:user_id] = nil session[:user_circumstance] = nil session[:remember_me] = false + end + def signout + self._do_signout if params[:r] redirect_to params[:r] else @@ -159,24 +162,24 @@ class UserController < ApplicationController end # Change password (XXX and perhaps later email) - requires email authentication - def signchange + def signchangepassword if @user and ((not session[:user_circumstance]) or (session[:user_circumstance] != "change_password")) # Not logged in via email, so send confirmation - params[:submitted_signchange_send_confirm] = true - params[:signchange] = { :email => @user.email } + params[:submitted_signchangepassword_send_confirm] = true + params[:signchangepassword] = { :email => @user.email } end - if params[:submitted_signchange_send_confirm] + if params[:submitted_signchangepassword_send_confirm] # They've entered the email, check it is OK and user exists - if not MySociety::Validate.is_valid_email(params[:signchange][:email]) + if not MySociety::Validate.is_valid_email(params[:signchangepassword][:email]) flash[:error] = "That doesn't look like a valid email address. Please check you have typed it correctly." - render :action => 'signchange_send_confirm' + render :action => 'signchangepassword_send_confirm' return end - user_signchange = User.find_user_by_email(params[:signchange][:email]) - if user_signchange - # Send email with login link to go to signchange page - url = signchange_url + user_signchangepassword = User.find_user_by_email(params[:signchangepassword][:email]) + if user_signchangepassword + # Send email with login link to go to signchangepassword page + url = signchangepassword_url if params[:pretoken] url += "?pretoken=" + params[:pretoken] end @@ -188,27 +191,27 @@ class UserController < ApplicationController }, :circumstance => "change_password" # special login that lets you change your password ) - post_redirect.user = user_signchange + post_redirect.user = user_signchangepassword post_redirect.save! url = confirm_url(:email_token => post_redirect.email_token) - UserMailer.deliver_confirm_login(user_signchange, post_redirect.reason_params, url) + UserMailer.deliver_confirm_login(user_signchangepassword, post_redirect.reason_params, url) else # User not found, but still show confirm page to not leak fact user exists end - render :action => 'signchange_confirm' + render :action => 'signchangepassword_confirm' elsif not @user # Not logged in, prompt for email - render :action => 'signchange_send_confirm' + render :action => 'signchangepassword_send_confirm' else # Logged in via special email change password link, so can offer form to change password raise "internal error" unless (session[:user_circumstance] == "change_password") - if params[:submitted_signchange_password] + if params[:submitted_signchangepassword_do] @user.password = params[:user][:password] @user.password_confirmation = params[:user][:password_confirmation] if not @user.valid? - render :action => 'signchange' + render :action => 'signchangepassword' else @user.save! flash[:notice] = "Your password has been changed." @@ -220,11 +223,71 @@ class UserController < ApplicationController end end else - render :action => 'signchange' + render :action => 'signchangepassword' end end end + # Change your email + def signchangeemail + if not authenticated?( + :web => "To change your email address used on WhatDoTheyKnow.com", + :email => "Then you can change your email address used on WhatDoTheyKnow.com", + :email_subject => "Change your email address used on WhatDoTheyKnow.com" + ) + # "authenticated?" has done the redirect to signin page for us + return + end + + if !params[:submitted_signchangeemail_do] + render :action => 'signchangeemail' + return + end + + @signchangeemail = ChangeEmailValidator.new(params[:signchangeemail]) + @signchangeemail.logged_in_user = @user + + if !@signchangeemail.valid? + render :action => 'signchangeemail' + return + end + + # if new email already in use, send email there saying what happened + user_alreadyexists = User.find_user_by_email(@signchangeemail.new_email) + if user_alreadyexists + UserMailer.deliver_changeemail_already_used(@user.email, @signchangeemail.new_email) + # it is important this screen looks the same as the one below, so + # you can't change to someone's email in order to tell if they are + # registered with that email on the site + render :action => 'signchangeemail_confirm' + return + end + + # if not already, send a confirmation link to the new email address which logs + # them into the old email's user account, but with special user_circumstance + if (not session[:user_circumstance]) or (session[:user_circumstance] != "change_email") + post_redirect = PostRedirect.new(:uri => signchangeemail_url(), :post_params => params, + :circumstance => "change_email" # special login that lets you change your email + ) + post_redirect.user = @user + post_redirect.save! + + url = confirm_url(:email_token => post_redirect.email_token) + UserMailer.deliver_changeemail_confirm(@user, @signchangeemail.new_email, url) + # it is important this screen looks the same as the one above, so + # you can't change to someone's email in order to tell if they are + # registered with that email on the site + render :action => 'signchangeemail_confirm' + return + end + + # circumstance is 'change_email', so can actually change the email + @user.email = @signchangeemail.new_email + @user.save! + flash[:notice] = "You have now changed your email address used on WhatDoTheyKnow.com" + redirect_to user_url(@user) + end + # Send a message to another user def contact @recipient_user = User.find(params[:id]) |