diff --git a/Gemfile b/Gemfile index 07e4f84c5..a8f26cd6f 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gem 'rails', '6.1.7.6' gem 'rake' gem 'pg' -gem 'authlogic' +gem 'devise' gem 'will_paginate' gem 'json' gem 'delayed_job_active_record' @@ -34,6 +34,9 @@ gem 'image_processing' gem 'maxminddb' gem 'redcarpet' gem 'scrypt' +gem 'omniauth' +gem 'omniauth-rails_csrf_protection' +gem 'omniauth-saml' gem 'aws-sdk-codedeploy' gem 'aws-sdk-cloudwatchlogs' diff --git a/Gemfile.lock b/Gemfile.lock index 5fa679a6f..6091084b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,11 +64,6 @@ GEM public_suffix (>= 2.0.2, < 6.0) appsignal (3.4.13) rack - authlogic (6.4.1) - activemodel (>= 5.2, < 6.2) - activerecord (>= 5.2, < 6.2) - activesupport (>= 5.2, < 6.2) - request_store (~> 1.0) aws-eventstream (1.1.1) aws-partitions (1.441.0) aws-sdk-cloudwatchlogs (1.6.0) @@ -140,6 +135,12 @@ GEM delayed_job_active_record (4.1.6) activerecord (>= 3.0, < 6.2) delayed_job (>= 3.0, < 5) + devise (4.9.3) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) diff-lcs (1.4.4) docile (1.3.1) dotenv (2.7.6) @@ -171,6 +172,7 @@ GEM globalid (1.2.1) activesupport (>= 6.1) hashdiff (1.0.1) + hashie (5.0.0) htmlentities (4.3.4) i18n (1.14.1) concurrent-ruby (~> 1.0) @@ -225,7 +227,7 @@ GEM timeout net-smtp (0.4.0) net-protocol - nio4r (2.5.9) + nio4r (2.7.0) nokogiri (1.15.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) @@ -235,15 +237,28 @@ GEM racc (~> 1.4) nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) + omniauth (2.1.1) + hashie (>= 3.4.6) + rack (>= 2.2.3) + rack-protection + omniauth-rails_csrf_protection (1.0.1) + actionpack (>= 4.2) + omniauth (~> 2.0) + omniauth-saml (2.1.0) + omniauth (~> 2.0) + ruby-saml (~> 1.12) + orm_adapter (0.5.0) pg (1.2.3) pry (0.13.1) coderay (~> 1.1) method_source (~> 1.0) public_suffix (5.0.3) - puma (5.6.7) + puma (5.6.8) nio4r (~> 2.0) racc (1.7.1) rack (2.2.8) + rack-protection (3.1.0) + rack (~> 2.2, >= 2.2.4) rack-test (2.1.0) rack (>= 1.3) rails (6.1.7.6) @@ -286,6 +301,9 @@ GEM regexp_parser (2.8.2) request_store (1.5.0) rack (>= 1.4) + responders (3.1.1) + actionpack (>= 5.2) + railties (>= 5.2) rexml (3.2.6) rspec-core (3.10.1) rspec-support (~> 3.10.0) @@ -304,6 +322,9 @@ GEM rspec-mocks (~> 3.10) rspec-support (~> 3.10) rspec-support (3.10.2) + ruby-saml (1.16.0) + nokogiri (>= 1.13.10) + rexml ruby-vips (2.1.4) ffi (~> 1.12) rubyzip (2.3.2) @@ -348,6 +369,8 @@ GEM concurrent-ruby (~> 1.0) uglifier (4.1.8) execjs (>= 0.3.0, < 3) + warden (1.2.9) + rack (>= 2.0.9) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -371,7 +394,6 @@ PLATFORMS DEPENDENCIES appsignal - authlogic aws-sdk-cloudwatchlogs aws-sdk-codedeploy aws-sdk-s3 @@ -387,6 +409,7 @@ DEPENDENCIES database_cleaner delayed-web delayed_job_active_record + devise dotenv-rails email_spec factory_bot_rails @@ -403,6 +426,9 @@ DEPENDENCIES maxminddb net-http-persistent nokogiri + omniauth + omniauth-rails_csrf_protection + omniauth-saml pg pry puma (< 6) diff --git a/README.md b/README.md index 34c3f5efc..acc8392d0 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,6 @@ We recommend using [Docker Desktop][2] to get setup quickly. If you'd prefer not docker compose run --rm web rake db:setup ``` -### Create an admin user - -``` -docker compose run --rm web rake epets:add_sysadmin_user -``` - ### Load the country list ``` diff --git a/app/assets/stylesheets/petitions/admin/_header.scss b/app/assets/stylesheets/petitions/admin/_header.scss index 25441550f..32d6d888e 100644 --- a/app/assets/stylesheets/petitions/admin/_header.scss +++ b/app/assets/stylesheets/petitions/admin/_header.scss @@ -9,6 +9,7 @@ float: right; } li { + color: $white; display: inline; margin-left: $gutter-one-third; a { diff --git a/app/controllers/admin/admin_controller.rb b/app/controllers/admin/admin_controller.rb index c92caabf4..555eec293 100644 --- a/app/controllers/admin/admin_controller.rb +++ b/app/controllers/admin/admin_controller.rb @@ -26,4 +26,12 @@ def set_appsignal_namespace def set_current_user Admin::Current.user = current_user end + + def after_sign_in_path_for(resource) + admin_root_url + end + + def after_sign_out_path_for(resource) + admin_login_url + end end diff --git a/app/controllers/admin/admin_users_controller.rb b/app/controllers/admin/admin_users_controller.rb index 3af497500..28ee372a3 100644 --- a/app/controllers/admin/admin_users_controller.rb +++ b/app/controllers/admin/admin_users_controller.rb @@ -1,67 +1,7 @@ class Admin::AdminUsersController < Admin::AdminController before_action :require_sysadmin - before_action :find_user, only: %i[edit update destroy] - - rescue_from AdminUser::CannotDeleteCurrentUser do - redirect_to admin_admin_users_url, alert: :user_is_current_user - end - - rescue_from AdminUser::MustBeAtLeastOneAdminUser do - redirect_to admin_admin_users_url, alert: :user_count_is_too_low - end - - rescue_from ActiveRecord::DeleteRestrictionError do - redirect_to admin_admin_users_url, alert: :user_has_moderated_petitions - end def index @users = AdminUser.by_name.paginate(page: params[:page], per_page: 50) end - - def new - @user = AdminUser.new - end - - def create - @user = AdminUser.new(admin_user_params) - - if @user.save - redirect_to admin_admin_users_url, notice: :user_created - else - render :new - end - end - - def edit - end - - def update - if @user.update(admin_user_params) - redirect_to admin_admin_users_url, notice: :user_updated - else - render :edit - end - end - - def destroy - if @user.destroy(current_user: current_user) - redirect_to admin_admin_users_url, notice: :user_deleted - else - redirect_to admin_admin_users_url, alert: :user_not_deleted - end - end - - protected - - def find_user - @user = AdminUser.find(params[:id]) - end - - def admin_user_params - params. - require(:admin_user). - permit(:password, :password_confirmation, :first_name, - :last_name, :role, :email, :force_password_reset, - :account_disabled) - end end diff --git a/app/controllers/admin/archived/government_response_controller.rb b/app/controllers/admin/archived/government_response_controller.rb index c479541cf..79650c65e 100644 --- a/app/controllers/admin/archived/government_response_controller.rb +++ b/app/controllers/admin/archived/government_response_controller.rb @@ -26,6 +26,16 @@ def update end end + def destroy + @government_response.destroy + + if @government_response.destroyed? + redirect_to admin_archived_petition_path(@petition), notice: :government_response_deleted + else + redirect_to admin_archived_petition_government_response_path(@petition), alert: :government_response_not_deleted + end + end + private def fetch_petition diff --git a/app/controllers/admin/government_response_controller.rb b/app/controllers/admin/government_response_controller.rb index ec3c888d6..8f3e5f25d 100644 --- a/app/controllers/admin/government_response_controller.rb +++ b/app/controllers/admin/government_response_controller.rb @@ -26,6 +26,16 @@ def update end end + def destroy + @government_response.destroy + + if @government_response.destroyed? + redirect_to admin_petition_path(@petition), notice: :government_response_deleted + else + redirect_to admin_petition_government_response_path(@petition), alert: :government_response_not_deleted + end + end + private def fetch_petition diff --git a/app/controllers/admin/omniauth_callbacks_controller.rb b/app/controllers/admin/omniauth_callbacks_controller.rb new file mode 100644 index 000000000..78a4fb705 --- /dev/null +++ b/app/controllers/admin/omniauth_callbacks_controller.rb @@ -0,0 +1,45 @@ +class Admin::OmniauthCallbacksController < Devise::OmniauthCallbacksController + skip_before_action :require_admin + skip_before_action :verify_authenticity_token, only: %i[saml] + + rescue_from ActiveRecord::RecordNotFound do + redirect_to admin_login_url, alert: :login_failed + end + + def saml + @user = AdminUser.find_or_create_from!(provider, auth_data) + + if @user.present? + sign_in @user, event: :authentication + + set_flash_message(:notice, :signed_in) + set_refresh_header + + render "admin/admin/index" + else + redirect_to admin_login_url, alert: :invalid_login + end + end + + def failure + redirect_to admin_login_url, alert: :login_failed + end + + private + + def after_omniauth_failure_path_for(scope) + admin_login_url + end + + def auth_data + request.env["omniauth.auth"] + end + + def provider + IdentityProvider.find_by!(name: auth_data.provider) + end + + def set_refresh_header + headers['Refresh'] = "0; url=#{admin_root_url}" + end +end diff --git a/app/controllers/admin/profile_controller.rb b/app/controllers/admin/profile_controller.rb deleted file mode 100644 index d45f59c13..000000000 --- a/app/controllers/admin/profile_controller.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Admin::ProfileController < Admin::AdminController - skip_before_action :check_for_password_change - - def edit - end - - def update - if current_user.update_with_password(admin_user_params) - redirect_to admin_root_url, notice: :password_updated - else - render :edit - end - end - - def admin_user_params - params.require(:admin_user).permit( - :current_password, :password, :password_confirmation - ) - end -end diff --git a/app/controllers/admin/sessions_controller.rb b/app/controllers/admin/sessions_controller.rb new file mode 100644 index 000000000..1df24f9e2 --- /dev/null +++ b/app/controllers/admin/sessions_controller.rb @@ -0,0 +1,52 @@ +class Admin::SessionsController < Devise::SessionsController + skip_before_action :require_admin, except: :continue + prepend_before_action :skip_timeout, only: :status + + helper_method :last_request_at + + def create + if provider? + redirect_to sso_provider_url(provider), status: :temporary_redirect + else + redirect_to admin_login_url, alert: :invalid_login + end + end + + def continue + respond_to do |format| + format.json + end + end + + def status + respond_to do |format| + format.json + end + end + + private + + def email_domain + Mail::Address.new(sign_in_params[:email]).domain + rescue Mail::Field::ParseError + nil + end + + def provider + @provider ||= IdentityProvider.find_by(domain: email_domain) + end + + def provider? + provider.present? + end + + def skip_timeout + request.env['devise.skip_trackable'] = true + end + + def last_request_at + if user_session && user_session.key?("last_request_at") + Time.at(user_session["last_request_at"]).in_time_zone + end + end +end diff --git a/app/controllers/admin/user_sessions_controller.rb b/app/controllers/admin/user_sessions_controller.rb deleted file mode 100644 index 976f31888..000000000 --- a/app/controllers/admin/user_sessions_controller.rb +++ /dev/null @@ -1,46 +0,0 @@ -class Admin::UserSessionsController < Admin::AdminController - skip_before_action :require_admin, only: [:new, :create, :destroy, :status] - skip_before_action :check_for_password_change - - before_action :reset_session, only: [:create, :destroy] - - def new - @user_session = AdminUserSession.new - end - - def create - @user_session = AdminUserSession.new(user_session_params) - - if @user_session.save - redirect_to_target_or_default - elsif @user_session.last_login_attempt? - render :new, alert: :last_login - elsif @user_session.being_brute_force_protected? - render :new, alert: :disabled_login - else - render :new, alert: :invalid_login - end - end - - def destroy - current_session.destroy if logged_in? - redirect_to admin_login_url, notice: :logged_out - end - - def status - end - - def continue - current_user.touch(:last_request_at) - end - - private - - def user_session_params - params.require(:admin_user_session).permit(:email, :password).to_h - end - - def last_request_update_allowed? - action_name != 'status' - end -end diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index fa6435912..2ae551c8b 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -2,32 +2,10 @@ module Authentication extend ActiveSupport::Concern included do - before_action :set_login_timeout - before_action :logout_stale_session - before_action :require_admin - before_action :check_for_password_change - - helper_method :current_user, :current_session, :logged_in? - end - - def current_session - return @current_session if defined?(@current_session) - @current_session = AdminUserSession.find - end - def current_user - return @current_user if defined?(@current_user) - @current_user = current_session && current_session.record - end - - def logged_in? - current_user - end - - def redirect_to_target_or_default - redirect_to(session[:return_to] || admin_root_url) - session[:return_to] = nil + alias_method :logged_in?, :user_signed_in? + helper_method :logged_in? end def require_admin @@ -36,12 +14,6 @@ def require_admin end end - def check_for_password_change - if current_user.has_to_change_password? - redirect_to edit_admin_profile_url(current_user), alert: :change_password - end - end - def require_moderator unless current_user.is_a_moderator? || current_user.is_a_sysadmin? redirect_to admin_root_url, alert: :moderator_required @@ -53,18 +25,4 @@ def require_sysadmin redirect_to admin_root_url, alert: :sysadmin_required end end - - private - - def store_target_location - session[:return_to] = request.fullpath - end - - def set_login_timeout - AdminUser.logged_in_timeout = Site.login_timeout - end - - def logout_stale_session - current_session.destroy if current_session && current_session.stale? - end end diff --git a/app/controllers/concerns/flash_i18n.rb b/app/controllers/concerns/flash_i18n.rb index a74acdc1a..33d6084bf 100644 --- a/app/controllers/concerns/flash_i18n.rb +++ b/app/controllers/concerns/flash_i18n.rb @@ -1,6 +1,4 @@ module FlashI18n - protected - def redirect_to(url, options = {}) self.class._flash_types.each do |flash_type| if options.key?(flash_type) @@ -17,6 +15,8 @@ def redirect_to(url, options = {}) super(url, options) end + protected + def translate_flash(key) if admin_request? scope = :"admin.flash" diff --git a/app/jobs/archive_petition_job.rb b/app/jobs/archive_petition_job.rb index 6165c1de1..b10e73557 100644 --- a/app/jobs/archive_petition_job.rb +++ b/app/jobs/archive_petition_job.rb @@ -75,6 +75,8 @@ def perform(petition) e.subject = email.subject e.body = email.body e.sent_by = email.sent_by + e.email_count = email.email_count + e.emails_enqueued_at = email.emails_enqueued_at e.created_at = email.created_at e.updated_at = email.updated_at end diff --git a/app/jobs/archived/email_petitioners_job.rb b/app/jobs/archived/email_petitioners_job.rb index 7901e6e73..ada6fa7dc 100644 --- a/app/jobs/archived/email_petitioners_job.rb +++ b/app/jobs/archived/email_petitioners_job.rb @@ -12,6 +12,13 @@ class EmailPetitionersJob < ApplicationJob log_exception(exception) end + after_enqueue_send_email_jobs do + email.update_columns( + email_count: petition.signatures.subscribers, + emails_enqueued_at: Time.current + ) + end + def perform(**args) @email = args[:email] super diff --git a/app/jobs/concerns/email_all_petition_signatories.rb b/app/jobs/concerns/email_all_petition_signatories.rb index f8febf1a8..6dc685777 100644 --- a/app/jobs/concerns/email_all_petition_signatories.rb +++ b/app/jobs/concerns/email_all_petition_signatories.rb @@ -13,6 +13,10 @@ module EmailAllPetitionSignatories attr_reader :petition, :requested_at, :scope queue_as :high_priority + + with_options(skip_after_callbacks_if_terminated: true) do + define_callbacks :enqueue_send_email_jobs + end end module ClassMethods @@ -23,6 +27,14 @@ def run_later_tonight(**args) set(wait_until: later_tonight).perform_later(**args.merge(requested_at: requested_at_iso8601)) end + def before_enqueue_send_email_jobs(*filters, &blk) + set_callback(:enqueue_send_email_jobs, :before, *filters, &blk) + end + + def after_enqueue_send_email_jobs(*filters, &blk) + set_callback(:enqueue_send_email_jobs, :after, *filters, &blk) + end + private def requested_at @@ -64,8 +76,10 @@ def perform(**args) # and enqueues a job to do the actual sending def enqueue_send_email_jobs Appsignal.without_instrumentation do - signatures_to_email.find_each do |signature| - email_delivery_job_class.perform_later(**mailer_arguments(signature)) + run_callbacks :enqueue_send_email_jobs do + signatures_to_email.find_each do |signature| + email_delivery_job_class.perform_later(**mailer_arguments(signature)) + end end end end diff --git a/app/jobs/email_petitioners_job.rb b/app/jobs/email_petitioners_job.rb index 764ec6c8e..11aecbd31 100644 --- a/app/jobs/email_petitioners_job.rb +++ b/app/jobs/email_petitioners_job.rb @@ -11,6 +11,13 @@ class EmailPetitionersJob < ApplicationJob log_exception(exception) end + after_enqueue_send_email_jobs do + email.update_columns( + email_count: petition.signatures.subscribers, + emails_enqueued_at: Time.current + ) + end + def perform(**args) @email = args[:email] super diff --git a/app/models/admin_user.rb b/app/models/admin_user.rb index 3ac906f3a..e95e38c22 100644 --- a/app/models/admin_user.rb +++ b/app/models/admin_user.rb @@ -1,27 +1,20 @@ class AdminUser < ActiveRecord::Base - DISABLED_LOGIN_COUNT = 5 SYSADMIN_ROLE = 'sysadmin' MODERATOR_ROLE = 'moderator' REVIEWER_ROLE = 'reviewer' ROLES = [SYSADMIN_ROLE, MODERATOR_ROLE, REVIEWER_ROLE] - PASSWORD_REGEX = /\A.*(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\W_]).*\z/ class CannotDeleteCurrentUser < RuntimeError; end class MustBeAtLeastOneAdminUser < RuntimeError; end - acts_as_authentic do |config| - config.crypto_provider = ::Authlogic::CryptoProviders::SCrypt + devise :trackable, :timeoutable - config.check_passwords_against_database = true - config.ignore_blank_passwords = true - config.logged_in_timeout = Site.login_timeout - config.require_password_confirmation = true - - config.validates_length_of :password, minimum: 8, unless: ->(u) { u.password.blank? } - config.validates_confirmation_of :password, unless: ->(u) { u.password.blank? } - config.validates :email, email: true, uniqueness: { case_sensitive: false } - config.validates_uniqueness_of :email, uniqueness: { case_sensitive: false } - end + # TODO: Drop these columns once rollout of SSO has been completed + self.ignored_columns = %i[ + encrypted_password password_salt + force_password_reset password_changed_at + failed_attempts locked_at + ] with_options dependent: :restrict_with_exception do with_options foreign_key: :moderated_by_id do @@ -31,48 +24,53 @@ class MustBeAtLeastOneAdminUser < RuntimeError; end end # = Validations = - validates_presence_of :email, :first_name, :last_name - validates_presence_of :password, on: :create - validates_format_of :password, with: PASSWORD_REGEX, allow_blank: true - validates_inclusion_of :role, in: ROLES + validates :first_name, :last_name, presence: true + validates :email, presence: true, email: true + validates :email, uniqueness: { case_sensitive: false } + validates :role, presence: true, inclusion: { in: ROLES } # = Callbacks = - before_update if: :crypted_password_changed? do - self.force_password_reset = false - self.password_changed_at = Time.current - end + before_save :reset_persistence_token, unless: :persistence_token? # = Finders = scope :by_name, -> { order(:last_name, :first_name) } scope :by_role, ->(role) { where(role: role).order(:id) } # = Methods = - def current_password - defined?(@current_password) ? @current_password : nil + def self.timeout_in + Site.login_timeout.seconds + end + + def self.find_or_create_from!(provider, auth_data) + find_or_create_by!(email: auth_data.fetch(:uid)) do |user| + user.first_name = auth_data.info.fetch(:first_name) + user.last_name = auth_data.info.fetch(:last_name) + groups = Array.wrap(auth_data.info.fetch(:groups)) + + if (groups & provider.sysadmins).any? + user.role = SYSADMIN_ROLE + elsif (groups & provider.moderators).any? + user.role = MODERATOR_ROLE + elsif (groups & provider.reviewers).any? + user.role = REVIEWER_ROLE + end + end + rescue ActiveRecord::RecordNotUnique => e + find_by!(email: auth_data.fetch(:uid)) + rescue ActiveRecord::RecordInvalid => e + Appsignal.send_exception(e) and return nil end - def current_password=(value) - @current_password = value + def reset_persistence_token + self.persistence_token = SecureRandom.hex(64) end - def update_with_password(attrs) - if attrs[:password].blank? - attrs.delete(:password) - attrs.delete(:password_confirmation) if attrs[:password_confirmation].blank? - end - - self.attributes = attrs - self.valid? - - if current_password.blank? - errors.add(:current_password, :blank) - elsif !valid_password?(current_password) - errors.add(:current_password, :invalid) - elsif current_password == password - errors.add(:password, :taken) - end + def reset_persistence_token! + SecureRandom.hex(64).tap { |token| update_column(:persistence_token, token) } + end - errors.empty? && save(validate: false) + def valid_persistence_token?(token) + persistence_token == token end def destroy(current_user: nil) @@ -109,10 +107,6 @@ def is_a_reviewer? self.role == 'reviewer' end - def has_to_change_password? - self.force_password_reset or (self.password_changed_at and self.password_changed_at < 9.months.ago) - end - def can_take_petitions_down? is_a_sysadmin? || is_a_moderator? end @@ -129,19 +123,11 @@ def can_moderate_petitions? is_a_sysadmin? || is_a_moderator? end - def account_disabled - self.failed_login_count >= DISABLED_LOGIN_COUNT - end - - def account_disabled=(flag) - self.failed_login_count = (flag == "0" or !flag) ? 0 : DISABLED_LOGIN_COUNT - end - - def elapsed_time(now = Time.current) + def elapsed_time(last_request_at, now = Time.current) (now - last_request_at).floor end - def time_remaining(now = Time.current) - [Site.login_timeout - elapsed_time(now), 0].max + def time_remaining(last_request_at, now = Time.current) + [Site.login_timeout - elapsed_time(last_request_at, now), 0].max end end diff --git a/app/models/admin_user_session.rb b/app/models/admin_user_session.rb deleted file mode 100644 index e11841576..000000000 --- a/app/models/admin_user_session.rb +++ /dev/null @@ -1,33 +0,0 @@ -class AdminUserSession < Authlogic::Session::Base - allow_http_basic_auth false - consecutive_failed_logins_limit AdminUser::DISABLED_LOGIN_COUNT - logout_on_timeout true - - before_save do - record.reset_persistence_token! - end - - before_destroy do - controller.reset_session - - if stale? - stale_record.reset_persistence_token! - else - record.reset_persistence_token! - end - end - - def last_login_attempt? - failed_login_count == consecutive_failed_logins_limit - 1 - end - - def time_remaining - record ? record.time_remaining : 0 - end - - private - - def failed_login_count - attempted_record.present? ? attempted_record.failed_login_count : 0 - end -end diff --git a/app/models/archived/government_response.rb b/app/models/archived/government_response.rb index 0e8802da0..97873ff17 100644 --- a/app/models/archived/government_response.rb +++ b/app/models/archived/government_response.rb @@ -13,6 +13,14 @@ class GovernmentResponse < ActiveRecord::Base petition.touch(:government_response_at) unless petition.government_response_at? end + after_destroy do + # This prevents any enqueued email jobs from being sent + petition.set_email_requested_at_for("government_response") + + # This removes the petition from the 'Government response' list + petition.update_columns(government_response_at: nil) + end + def responded_on super || default_responded_on end diff --git a/app/models/archived/signature.rb b/app/models/archived/signature.rb index 1cb34d5e7..6270dc67e 100644 --- a/app/models/archived/signature.rb +++ b/app/models/archived/signature.rb @@ -108,6 +108,10 @@ def subscribed where(notify_by_email: true) end + def subscribers + validated.subscribed.count + end + def validated where(state: VALIDATED_STATE) end diff --git a/app/models/concerns/perishable_token_generator.rb b/app/models/concerns/perishable_token_generator.rb index f591113af..47bf9858c 100644 --- a/app/models/concerns/perishable_token_generator.rb +++ b/app/models/concerns/perishable_token_generator.rb @@ -4,7 +4,7 @@ module PerishableTokenGenerator class_methods do def has_perishable_token(called: 'perishable_token') before_create do - write_attribute(called, Authlogic::Random.friendly_token) + write_attribute(called, SecureRandom.base58(20)) end end end diff --git a/app/models/government_response.rb b/app/models/government_response.rb index af9eaea70..af01a709a 100644 --- a/app/models/government_response.rb +++ b/app/models/government_response.rb @@ -11,6 +11,18 @@ class GovernmentResponse < ActiveRecord::Base petition.touch(:government_response_at) unless petition.government_response_at? end + after_destroy do + unless petition.archived? + Appsignal.increment_counter("petition.responded", -1) + + # This prevents any enqueued email jobs from being sent + petition.set_email_requested_at_for("government_response") + + # This removes the petition from the 'Government response' list + petition.update_columns(government_response_at: nil) + end + end + def responded_on super || default_responded_on end diff --git a/app/models/identity_provider.rb b/app/models/identity_provider.rb new file mode 100644 index 000000000..67025cfb4 --- /dev/null +++ b/app/models/identity_provider.rb @@ -0,0 +1,100 @@ +class IdentityProvider + class ProviderNotFound < ArgumentError; end + + class << self + delegate :each, to: :providers + + def providers + @providers ||= load_providers + end + + def names + providers.map(&:name) + end + + def find_by(domain:) + providers.detect { |provider| provider.domains.include?(domain) } + end + + def find_by!(name:) + providers.detect { |provider| provider.name.to_s == name } || raise_provider_not_found(name) + end + + private + + def load_providers + Rails.application.config_for(:sso).map { |options| IdentityProvider.new(options) } + end + + def raise_provider_not_found(name) + raise ProviderNotFound, "Couldn't find the provider '#{name}'" + end + end + + attr_reader :name, :attribute_statements + attr_reader :assertion_consumer_service_url, :sp_entity_id + attr_reader :idp_sso_service_url, :idp_cert, :domains + attr_reader :sysadmins, :moderators, :reviewers + + def initialize(options) + @name = options.fetch(:name).to_sym + @attribute_statements = options.fetch(:attributes, default_attributes) + @assertion_consumer_service_url = "#{Site.moderate_url}/admin/auth/#{name}/callback" + @sp_entity_id = "#{Site.moderate_url}/admin/auth/#{name}" + @idp_sso_service_url = options.fetch(:idp_sso_service_url) + @idp_cert = options.fetch(:idp_cert, "") + @domains = options.fetch(:domains) + @sysadmins = options.fetch(:sysadmins, []) + @moderators = options.fetch(:moderators, []) + @reviewers = options.fetch(:reviewers, []) + + unless klass_defined? + strategies.const_set(klass, new_klass) + end + end + + def to_param + name.to_s + end + + def config + { + attribute_statements: attribute_statements, + assertion_consumer_service_url: assertion_consumer_service_url, + sp_entity_id: sp_entity_id, + idp_sso_service_url: idp_sso_service_url, + idp_cert: idp_cert + } + end + + private + + def default_attributes + { + email: ["email"], + first_name: ["first_name"], + last_name: ["last_name"], + groups: ["groups"] + } + end + + def strategies + OmniAuth::Strategies + end + + def parent_klass + OmniAuth::Strategies::SAML + end + + def new_klass + Class.new(parent_klass) + end + + def klass + @klass ||= name.to_s.camelize.to_sym + end + + def klass_defined? + strategies.const_defined?(klass, false) + end +end diff --git a/app/models/signature.rb b/app/models/signature.rb index 605f31407..1a0944099 100644 --- a/app/models/signature.rb +++ b/app/models/signature.rb @@ -623,7 +623,7 @@ def validate!(now = Time.current, force: false, request: nil) end unless signed_token? - attributes[:signed_token] = Authlogic::Random.friendly_token + attributes[:signed_token] = SecureRandom.base58(20) end update_columns(attributes) @@ -820,7 +820,7 @@ def generate_uuid end def generate_and_save_signed_token - token = Authlogic::Random.friendly_token + token = SecureRandom.base58(20) retry_lock do if signed_token? diff --git a/app/models/site.rb b/app/models/site.rb index 615d400e2..e56e4084b 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -86,6 +86,14 @@ def moderate_host_with_port instance.moderate_host_with_port end + def moderate_url + if table_exists? + instance.moderate_url + else + default_moderate_url + end + end + def constraints_for_moderation if table_exists? instance.constraints_for_moderation diff --git a/app/views/admin/admin_users/_form.html.erb b/app/views/admin/admin_users/_form.html.erb deleted file mode 100644 index 458be9fab..000000000 --- a/app/views/admin/admin_users/_form.html.erb +++ /dev/null @@ -1,65 +0,0 @@ -<%= form_for [:admin, @user] do |f| -%> - <%= form_row for: [f.object, :first_name] do %> - <%= f.label :first_name, class: 'form-label form-label-inline' %><%= mandatory_field %> - <%= error_messages_for_field f.object, :first_name %> - <%= f.text_field :first_name, tabindex: increment, class: 'form-control', autofocus: 'autofocus' %> - <% end %> - - <%= form_row for: [f.object, :last_name] do %> - <%= f.label :last_name, class: 'form-label form-label-inline' %><%= mandatory_field %> - <%= error_messages_for_field f.object, :last_name %> - <%= f.text_field :last_name, tabindex: increment, class: 'form-control' %> - <% end %> - - <%= form_row for: [f.object, :email] do %> - <%= f.label :email, class: 'form-label form-label-inline' %><%= mandatory_field %> - <%= error_messages_for_field f.object, :email %> - <%= f.email_field :email, tabindex: increment, class: 'form-control' %> - <% end %> - - <%= form_row for: [f.object, :email], class: 'inline' do %> - <%= f.label :role, class: 'form-label' %> - <%= error_messages_for_field f.object, :role %> - <% AdminUser::ROLES.each do |role| %> -
Name | Role | -Disabled | -+ | Last login | ||
---|---|---|---|---|---|---|
<%= link_to user.name, edit_admin_admin_user_path(user) %> | +<%= user.name%> | <%= mail_to user.email %> | <%= user.role %> | -<%= user.account_disabled ? 'Yes' : '' %> | -- <% if user != current_user %> - <%= cms_delete_link user, :url => admin_admin_user_path(user) %> - <% else %> - - <% end %> - | +<%= short_date_time_format(user.current_sign_in_at) %> |
Subject | +Count | +Sent | Actions | <%= email.subject %> | <%= button_to 'Edit', edit_admin_archived_petition_email_path(petition, email), method: :get, class: 'button' %> + | <%= number_with_delimiter(email.email_count) || "–" %> | +<%= date_format(email.emails_enqueued_at) || "–" %> | <%= button_to 'Delete', admin_archived_petition_email_path(petition, email), method: :delete, class: 'button-warning', data: { confirm: 'Delete other parliamentary business?' } %> diff --git a/app/views/admin/government_response/_petition_action_government_response.html.erb b/app/views/admin/government_response/_petition_action_government_response.html.erb index 442a8480c..0cddcc753 100644 --- a/app/views/admin/government_response/_petition_action_government_response.html.erb +++ b/app/views/admin/government_response/_petition_action_government_response.html.erb @@ -21,8 +21,11 @@ <% end %> <%= email_petitioners_with_count_submit_button(f, petition, disabled: @petition.editing_disabled?) %> - <%= f.submit "Save", name: 'save', class: 'button-secondary', disabled: @petition.editing_disabled? %> - <%= link_to 'Cancel', admin_petition_path(@petition), class: 'button-secondary' %> + <% if @government_response.persisted? %> + <%= link_to "Delete", admin_petition_government_response_path(@petition), class: "button-warning", method: :delete, data: { confirm: "Are you sure you want to delete the government response?" } %> + <% end %> + <%= f.submit "Save", name: "save", class: "button-secondary", disabled: @petition.editing_disabled? %> + <%= link_to "Cancel", admin_petition_path(@petition), class: "button-secondary" %> <% end %> <%= javascript_include_tag 'character-counter' %> diff --git a/app/views/admin/petition_emails/_petition_action_email_petitioners.html.erb b/app/views/admin/petition_emails/_petition_action_email_petitioners.html.erb index 6e975e224..fa5d90405 100644 --- a/app/views/admin/petition_emails/_petition_action_email_petitioners.html.erb +++ b/app/views/admin/petition_emails/_petition_action_email_petitioners.html.erb @@ -5,6 +5,8 @@
Subject | +Count | +Sent | Actions | |||
<%= email.subject %> | +<%= number_with_delimiter(email.email_count) || "–" %> | +<%= date_format(email.emails_enqueued_at) || "–" %> |
<%= button_to 'Edit', edit_admin_petition_email_path(petition, email), method: :get, class: 'button', disabled: @petition.editing_disabled? %>
<%= button_to 'Delete', admin_petition_email_path(petition, email), method: :delete, class: 'button-warning', disabled: @petition.editing_disabled?, data: { confirm: 'Delete other parliamentary business?' } %>
diff --git a/app/views/admin/profile/edit.html.erb b/app/views/admin/profile/edit.html.erb
deleted file mode 100644
index 0182b5982..000000000
--- a/app/views/admin/profile/edit.html.erb
+++ /dev/null
@@ -1,26 +0,0 @@
-Change your password-
-
diff --git a/app/views/admin/sessions/continue.json.jbuilder b/app/views/admin/sessions/continue.json.jbuilder
new file mode 100644
index 000000000..857102f0b
--- /dev/null
+++ b/app/views/admin/sessions/continue.json.jbuilder
@@ -0,0 +1 @@
+json.last_request_at last_request_at
diff --git a/app/views/admin/sessions/new.html.erb b/app/views/admin/sessions/new.html.erb
new file mode 100644
index 000000000..3f80f9479
--- /dev/null
+++ b/app/views/admin/sessions/new.html.erb
@@ -0,0 +1,13 @@
+
- <%= form_for [:admin, current_user], url: admin_profile_path(current_user) do |f| -%>
- <%= form_row for: [f.object, :current_password] do %>
- <%= f.label :current_password, class: 'form-label' %>
- <%= error_messages_for_field f.object, :current_password %>
- <%= f.password_field :current_password, tabindex: increment, class: 'form-control', autofocus: 'autofocus', autocomplete: 'off' %>
- <% end %>
-
- <%= form_row for: [f.object, :password] do %>
- <%= f.label :password, 'New password', class: 'form-label' %>
- <%= error_messages_for_field f.object, :password %>
- <%= f.password_field :password, tabindex: increment, class: 'form-control', autocomplete: 'off' %>
- <% end %>
-
- <%= form_row for: [f.object, :password_confirmation] do %>
- <%= f.label :password_confirmation, class: 'form-label' %>
- <%= error_messages_for_field f.object, :password_confirmation %>
- <%= f.password_field :password_confirmation, tabindex: increment, class: 'form-control', autocomplete: 'off' %>
- <% end %>
-
- <%= f.submit 'Save', class: "button" %>
- <% end -%>
-
-Sign in+
+
diff --git a/app/views/admin/sessions/status.json.jbuilder b/app/views/admin/sessions/status.json.jbuilder
new file mode 100644
index 000000000..804614345
--- /dev/null
+++ b/app/views/admin/sessions/status.json.jbuilder
@@ -0,0 +1,5 @@
+if current_user
+ json.time_remaining current_user.time_remaining(last_request_at)
+else
+ json.time_remaining 0
+end
diff --git a/app/views/admin/shared/_header_user_actions.html.erb b/app/views/admin/shared/_header_user_actions.html.erb
index 41cbe6bd8..d4024db58 100644
--- a/app/views/admin/shared/_header_user_actions.html.erb
+++ b/app/views/admin/shared/_header_user_actions.html.erb
@@ -1,6 +1,6 @@
<% if current_user %>
+ <%= form_for(@user, as: :user, url: admin_login_path, authenticity_token: form_authenticity_token) do |form| %>
+
+
+ <%= form.label :email, "Email", class: "form-label" %>
+ <%= form.text_field :email, class: "form-control", type: "email", autofocus: "autofocus" %>
+
+
+ <%= form.submit "Sign in", class: "button" %>
+ <% end %>
+
Sign in-
-
diff --git a/app/views/admin/user_sessions/status.json.jbuilder b/app/views/admin/user_sessions/status.json.jbuilder
deleted file mode 100644
index 618c77b4c..000000000
--- a/app/views/admin/user_sessions/status.json.jbuilder
+++ /dev/null
@@ -1,5 +0,0 @@
-if current_session
- json.time_remaining current_session.time_remaining
-else
- json.time_remaining 0
-end
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index 3c5131577..59e71e92b 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -128,26 +128,6 @@
"user_input": null,
"confidence": "Medium",
"note": ""
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "f523941f55e48c9af1dda1b71dd47c35e8cd6bce092c90e04bc940d257bf44e8",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/admin/admin_users_controller.rb",
- "line": 64,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.require(:admin_user).permit(:password, :password_confirmation, :first_name, :last_name, :role, :email, :force_password_reset, :account_disabled)",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Admin::AdminUsersController",
- "method": "admin_user_params"
- },
- "user_input": ":role",
- "confidence": "Medium",
- "note": ""
}
],
"updated": "2023-03-30 00:48:36 +0100",
diff --git a/config/initializers/delayed_web.rb b/config/initializers/delayed_web.rb
index 582345de3..d0ace5844 100644
--- a/config/initializers/delayed_web.rb
+++ b/config/initializers/delayed_web.rb
@@ -1,19 +1,31 @@
# Tell Delayed::Web that we're using ActiveRecord as our backend.
Rails.application.config.to_prepare do
Delayed::Web::Job.backend = 'active_record'
-end
-# Authenticate our delayed job web interface
-Delayed::Web::ApplicationController.class_eval do
- include Authentication, FlashI18n
+ # Authenticate our delayed job web interface
+ Delayed::Web::ApplicationController.class_eval do
+ include FlashI18n
- def admin_request?
- true
- end
+ before_action :require_admin
+
+ def admin_request?
+ true
+ end
+
+ protected
+
+ def admin_login_url
+ main_app.admin_login_url
+ end
- protected
+ def current_user
+ @current_user ||= warden.authenticate(scope: :user)
+ end
- def admin_login_url
- main_app.admin_login_url
+ def require_admin
+ unless current_user
+ redirect_to admin_login_url, alert: :admin_required
+ end
+ end
end
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
new file mode 100644
index 000000000..204e689f1
--- /dev/null
+++ b/config/initializers/devise.rb
@@ -0,0 +1,61 @@
+Devise.setup do |config|
+ # ==> Controller configuration
+ config.parent_controller = 'Admin::AdminController'
+
+ # ==> Mailer Configuration
+ config.mailer_sender = 'petitionscommittee@parliament.uk'
+ config.mailer = 'Devise::Mailer'
+ config.parent_mailer = 'ActionMailer::Base'
+
+ # ==> ORM configuration
+ require 'devise/orm/active_record'
+
+ # ==> Configuration for any authentication mechanism
+ config.authentication_keys = [:email]
+ config.case_insensitive_keys = [:email]
+ config.strip_whitespace_keys = [:email]
+
+ # ==> Configuration for :timeoutable
+ # This is overidden on the AdminUser model to use Site.login_timeout
+ config.timeout_in = 24.hours
+
+ # ==> Navigation configuration
+ config.sign_out_via = :get
+
+ # ==> Warden configuration
+ # Reset the token after logging in so that other sessions are logged out
+ Warden::Manager.after_set_user except: :fetch do |user, warden, options|
+ if warden.authenticated?(:user)
+ session = warden.session(:user)
+ session['persistence_token'] = user.reset_persistence_token!
+ end
+ end
+
+ # Logout the user if the token doesn't match what's in the session
+ Warden::Manager.after_set_user only: :fetch do |user, warden, options|
+ if warden.authenticated?(:user) && options[:store] != false
+ session = warden.session(:user)
+
+ unless user.valid_persistence_token?(session['persistence_token'])
+ warden.raw_session.clear
+ warden.logout(:user)
+
+ throw :warden, scope: :user, message: :other_login
+ end
+ end
+ end
+
+ # The devise failure app redirects to self when a session times out
+ # which causes issues with flash messages, etc. so change the message
+ # in the options hash to trigger the alternative path.
+ Warden::Manager.before_failure do |env, options|
+ if options[:message] == :timeout
+ options[:message] = :timedout
+ end
+ end
+
+ # Reset the token after logging out
+ Warden::Manager.before_logout do |user, warden, options|
+ user && user.reset_persistence_token!
+ end
+end
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
new file mode 100644
index 000000000..22bf0a143
--- /dev/null
+++ b/config/initializers/omniauth.rb
@@ -0,0 +1,10 @@
+Rails.application.config.middleware.use OmniAuth::Builder do
+ configure do |config|
+ config.path_prefix = '/admin/auth'
+ config.logger = Rails.logger
+ end
+
+ IdentityProvider.each do |idp|
+ provider idp.name, idp.config
+ end
+end
diff --git a/config/locales/activerecord.en-GB.yml b/config/locales/activerecord.en-GB.yml
index 18bda7f12..30fb9e9c2 100644
--- a/config/locales/activerecord.en-GB.yml
+++ b/config/locales/activerecord.en-GB.yml
@@ -36,13 +36,6 @@ en-GB:
models:
admin_user:
attributes:
- current_password:
- invalid: "Current password is incorrect"
- password:
- invalid: "Password must contain at least one digit, a lower and upper case letter and a special character"
- taken: "Password is the same as the current password"
- password_confirmation:
- confirmation: "Password confirmation doesn't match password"
role:
inclusion: "Role '%{value}' is invalid"
diff --git a/config/locales/admin.en-GB.yml b/config/locales/admin.en-GB.yml
index afed6feeb..abbaf34a5 100644
--- a/config/locales/admin.en-GB.yml
+++ b/config/locales/admin.en-GB.yml
@@ -54,6 +54,8 @@ en-GB:
email_resent_to_creator: "Resent the email to the petition creator and forwarded a copy to the feedback address"
email_sent_overnight: "Email will be sent overnight"
enqueued_petition_statistics_update: "Updating the petition statistics - please wait a few minutes and then refresh this page"
+ government_response_deleted: "Deleted government response successfully"
+ government_response_not_deleted: "Unable to delete government response - please contact support"
government_response_updated: "Updated government response successfully"
failed_tasks: "There was a problem starting the tasks - please contact support"
invalidation_cant_be_cancelled: "Can't cancel invalidations that have completed"
@@ -71,9 +73,8 @@ en-GB:
invalidation_removed: "Invalidation removed successfully"
invalidation_started: "Enqueued the invalidation %{summary}"
invalidation_updated: "Invalidation updated successfully"
- last_login: "You have one more attempt before your account is temporarily disabled"
- disabled_login: "Failed login limit exceeded, your account has been temporarily disabled"
- invalid_login: "Invalid email/password combination"
+ invalid_login: "Invalid login details"
+ login_failed: "There was a problem logging in - please contact support"
logged_out: "You have been logged out"
moderator_required: "You must be logged in as a moderator or system administrator to view this page"
moderation_delay_sent: "An email has been sent to creators that moderation has been delayed"
diff --git a/config/locales/devise.en-GB.yml b/config/locales/devise.en-GB.yml
new file mode 100644
index 000000000..cd2e5af47
--- /dev/null
+++ b/config/locales/devise.en-GB.yml
@@ -0,0 +1,18 @@
+# Additional translations at https://github.com/heartcombo/devise/wiki/I18n
+
+en:
+ devise:
+ failure:
+ already_authenticated: "You are already logged in"
+ invalid: "Invalid email/password combination"
+ locked: "Failed login limit exceeded, your account has been temporarily disabled"
+ last_attempt: "You have one more attempt before your account is temporarily disabled"
+ other_login: "You have been logged out"
+ not_found_in_database: "Invalid email/password combination"
+ timedout: "Your session expired. Please log in again to continue"
+ omniauth_callbacks:
+ signed_in: "Logged in successfully"
+ sessions:
+ signed_in: "Logged in successfully"
+ signed_out: "You have been logged out"
+ already_signed_out: "You have been logged out"
diff --git a/config/routes.rb b/config/routes.rb
index 697461023..e50ae9697 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -130,9 +130,8 @@
resource :parliament, only: %i[show update]
resource :search, only: %i[show]
- resources :admin_users
+ resources :admin_users, only: %[index]
resources :profile, only: %i[edit update]
- resources :user_sessions, only: %i[create]
resources :invalidations, except: %i[show] do
post :cancel, :count, :start, on: :member
@@ -152,7 +151,6 @@
scope only: %i[show update] do
resource :debate_outcome, path: 'debate-outcome'
- resource :government_response, path: 'government-response', controller: 'government_response'
resource :notes
resource :details, controller: 'petition_details'
resource :schedule_debate, path: 'schedule-debate', controller: 'schedule_debate'
@@ -163,6 +161,10 @@
resource :removal, controller: 'petition_removals'
end
+ scope only: %i[show update destroy] do
+ resource :government_response, path: 'government-response', controller: 'government_response'
+ end
+
resources :signatures, only: %i[index destroy] do
post :validate, :invalidate, on: :member
post :subscribe, :unsubscribe, on: :member
@@ -213,7 +215,6 @@
scope only: %i[show update] do
resource :debate_outcome, path: 'debate-outcome'
- resource :government_response, path: 'government-response', controller: 'government_response'
resource :notes
resource :details, controller: 'petition_details'
resource :schedule_debate, path: 'schedule-debate', controller: 'schedule_debate'
@@ -222,6 +223,10 @@
resource :topics, controller: 'petition_topics'
resource :removal, controller: 'petition_removals'
end
+
+ scope only: %i[show update destroy] do
+ resource :government_response, path: 'government-response', controller: 'government_response'
+ end
end
resources :signatures, only: %i[index destroy] do
@@ -239,15 +244,32 @@
get '/', action: 'index', as: :stats
post '/', action: 'create', as: nil
end
+ end
+
+ devise_for :users, class_name: 'AdminUser', module: 'admin', skip: %i[sessions]
- controller 'user_sessions' do
- get '/logout', action: 'destroy'
- get '/login', action: 'new'
- get '/continue', action: 'continue'
- get '/status', action: 'status'
+ as :user do
+ controller 'admin/sessions' do
+ get '/admin/login', action: 'new'
+ post '/admin/login', action: 'create', as: nil
+ get '/admin/logout', action: 'destroy'
+ get '/admin/continue', action: 'continue'
+ get '/admin/status', action: 'status'
+ end
+
+ controller 'admin/omniauth_callbacks' do
+ get '/admin/auth/failure', action: 'failure'
+
+ scope '/admin/auth/:provider', via: %i[get post] do
+ match '/', action: 'passthru', as: :sso_provider
+ match '/callback', action: 'saml', as: :sso_provider_callback
+ end
end
end
end
+ # Devise needs a `new_user_session_url` helper for its failure app
+ direct(:new_user_session) { route_for(:admin_login) }
+
get 'ping', to: 'ping#ping'
end
diff --git a/config/sso.yml b/config/sso.yml
new file mode 100644
index 000000000..e774e8c3e
--- /dev/null
+++ b/config/sso.yml
@@ -0,0 +1,13 @@
+development: []
+
+test:
+ - name: "example"
+ idp_sso_service_url: "http://localhost:3000/sso"
+ domains:
+ - "example.com"
+ sysadmins:
+ - "sysadmins"
+ moderators:
+ - "moderators"
+ reviewers:
+ - "reviewers"
diff --git a/db/migrate/20231215151430_authlogic_to_devise.rb b/db/migrate/20231215151430_authlogic_to_devise.rb
new file mode 100644
index 000000000..af9d78d10
--- /dev/null
+++ b/db/migrate/20231215151430_authlogic_to_devise.rb
@@ -0,0 +1,27 @@
+class AuthlogicToDevise < ActiveRecord::Migration[6.1]
+ def up
+ rename_column :admin_users, :crypted_password, :encrypted_password
+ rename_column :admin_users, :login_count, :sign_in_count
+ rename_column :admin_users, :current_login_at, :current_sign_in_at
+ rename_column :admin_users, :last_login_at, :last_sign_in_at
+ rename_column :admin_users, :current_login_ip, :current_sign_in_ip
+ rename_column :admin_users, :last_login_ip, :last_sign_in_ip
+ rename_column :admin_users, :failed_login_count, :failed_attempts
+
+ add_column :admin_users, :locked_at, :timestamp
+ remove_column :admin_users, :last_request_at
+ end
+
+ def down
+ rename_column :admin_users, :encrypted_password, :crypted_password
+ rename_column :admin_users, :sign_in_count, :login_count
+ rename_column :admin_users, :current_sign_in_at, :current_login_at
+ rename_column :admin_users, :last_sign_in_at, :last_login_at
+ rename_column :admin_users, :current_sign_in_ip, :current_login_ip
+ rename_column :admin_users, :last_sign_in_ip, :last_login_ip
+ rename_column :admin_users, :failed_attempts, :failed_login_count
+
+ remove_column :admin_users, :locked_at
+ add_column :admin_users, :last_request_at, :timestamp
+ end
+end
diff --git a/db/migrate/20240117134550_add_email_count_to_other_parliamentary_business.rb b/db/migrate/20240117134550_add_email_count_to_other_parliamentary_business.rb
new file mode 100644
index 000000000..f8989f9fc
--- /dev/null
+++ b/db/migrate/20240117134550_add_email_count_to_other_parliamentary_business.rb
@@ -0,0 +1,6 @@
+class AddEmailCountToOtherParliamentaryBusiness < ActiveRecord::Migration[6.1]
+ def change
+ add_column :petition_emails, :email_count, :integer
+ add_column :petition_emails, :emails_enqueued_at, :datetime
+ end
+end
diff --git a/db/migrate/20240117150804_add_email_count_to_other_parliamentary_business_archived.rb b/db/migrate/20240117150804_add_email_count_to_other_parliamentary_business_archived.rb
new file mode 100644
index 000000000..bee2afa38
--- /dev/null
+++ b/db/migrate/20240117150804_add_email_count_to_other_parliamentary_business_archived.rb
@@ -0,0 +1,6 @@
+class AddEmailCountToOtherParliamentaryBusinessArchived < ActiveRecord::Migration[6.1]
+ def change
+ add_column :archived_petition_emails, :email_count, :integer
+ add_column :archived_petition_emails, :emails_enqueued_at, :datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6fa87c42f..b7caf0033 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2023_03_27_145255) do
+ActiveRecord::Schema.define(version: 2024_01_17_150804) do
# These are extensions that must be enabled in order to support this database
enable_extension "intarray"
@@ -47,14 +47,14 @@
create_table "admin_users", id: :serial, force: :cascade do |t|
t.string "email", limit: 255, null: false
t.string "persistence_token", limit: 255
- t.string "crypted_password", limit: 255
+ t.string "encrypted_password", limit: 255
t.string "password_salt", limit: 255
- t.integer "login_count", default: 0
- t.integer "failed_login_count", default: 0
- t.datetime "current_login_at"
- t.datetime "last_login_at"
- t.string "current_login_ip", limit: 255
- t.string "last_login_ip", limit: 255
+ t.integer "sign_in_count", default: 0
+ t.integer "failed_attempts", default: 0
+ t.datetime "current_sign_in_at"
+ t.datetime "last_sign_in_at"
+ t.string "current_sign_in_ip", limit: 255
+ t.string "last_sign_in_ip", limit: 255
t.string "first_name", limit: 255
t.string "last_name", limit: 255
t.string "role", limit: 10, null: false
@@ -62,7 +62,7 @@
t.datetime "password_changed_at"
t.datetime "created_at"
t.datetime "updated_at"
- t.datetime "last_request_at"
+ t.datetime "locked_at"
t.index ["email"], name: "index_admin_users_on_email", unique: true
t.index ["last_name", "first_name"], name: "index_admin_users_on_last_name_and_first_name"
end
@@ -112,6 +112,8 @@
t.string "sent_by"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.integer "email_count"
+ t.datetime "emails_enqueued_at"
t.index ["petition_id"], name: "index_archived_petition_emails_on_petition_id"
end
@@ -475,6 +477,8 @@
t.string "sent_by"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.integer "email_count"
+ t.datetime "emails_enqueued_at"
t.index ["petition_id"], name: "index_petition_emails_on_petition_id"
end
diff --git a/docker/mailcatcher/Dockerfile b/docker/mailcatcher/Dockerfile
index d29cadecd..4481d6817 100644
--- a/docker/mailcatcher/Dockerfile
+++ b/docker/mailcatcher/Dockerfile
@@ -2,7 +2,8 @@ FROM ruby:2.7
ARG VERSION=0.8.2
-RUN gem install mailcatcher -v $VERSION
+RUN gem install sqlite3 -v 1.6.9 && \
+ gem install mailcatcher -v $VERSION
EXPOSE 1025 1080
diff --git a/features/admin/admin_access.feature b/features/admin/admin_access.feature
index 3a7c1d88f..1c44c96c0 100644
--- a/features/admin/admin_access.feature
+++ b/features/admin/admin_access.feature
@@ -15,11 +15,11 @@ Feature: Restricted access to the admin console
And I should be connected to the server via an ssl connection
And the markup should be valid
+ @javascript
Scenario: Login and logout to the admin console as a sysadmin
- Given a sysadmin user exists with first_name: "John", last_name: "Admin", email: "admin@example.com", password: "Letmein1!", password_confirmation: "Letmein1!"
+ Given a sysadmin SSO user exists
When I go to the admin login page
- And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "Letmein1!"
+ And I fill in "Email" with "sysadmin@example.com"
And I press "Sign in"
Then I should be on the admin home page
And I should be connected to the server via an ssl connection
@@ -29,74 +29,54 @@ Feature: Restricted access to the admin console
And I follow "Logout"
And I should be on the admin login page
- Scenario: Login and logout to the admin console as a moderator user
- Given a moderator user exists with first_name: "John", last_name: "Moderator", email: "admin@example.com", password: "Letmein1!", password_confirmation: "Letmein1!"
+ @javascript
+ Scenario: Login and logout to the admin console as a moderator
+ Given a moderator SSO user exists
When I go to the admin login page
- And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "Letmein1!"
+ And I fill in "Email" with "moderator@example.com"
And I press "Sign in"
Then I should be on the admin home page
- And I should see "John Moderator"
+ And I should be connected to the server via an ssl connection
+ And the markup should be valid
And I should not see "Users"
+ And I should see "John Moderator"
And I follow "Logout"
And I should be on the admin login page
- Scenario: Invalid login
- Given I go to the admin login page
- And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "letmein1"
+ @javascript
+ Scenario: Login and logout to the admin console as a reviewer
+ Given a reviewer SSO user exists
+ When I go to the admin login page
+ And I fill in "Email" with "reviewer@example.com"
And I press "Sign in"
- Then I should see "Invalid email/password combination"
- And should not see "Logout"
+ Then I should be on the admin home page
+ And I should be connected to the server via an ssl connection
+ And the markup should be valid
+ And I should not see "Users"
+ And I should see "John Reviewer"
+ And I follow "Logout"
+ And I should be on the admin login page
- Scenario: 5 failed logins disables an account
- Given a moderator user exists with email: "admin@example.com", password: "Letmein1!", password_confirmation: "Letmein1!"
- And I go to the admin login page
- And I try the password "wrong trousers" 3 times in a row
- And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "wrong trousers"
- And I press "Sign in"
- Then I should see "You have one more attempt before your account is temporarily disabled"
- And should not see "Logout"
- And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "wrong trousers"
+ Scenario: Invalid domain
+ When I go to the admin login page
+ And I fill in "Email" with "admin@example.org"
And I press "Sign in"
- Then I should see "Failed login limit exceeded, your account has been temporarily disabled"
+ Then I should see "Invalid login details"
And should not see "Logout"
- Scenario: 5 failed logins with an email address containing a wildcard does not disable an account
- Given a moderator user exists with email: "admin@example.com", password: "Letmein1!", password_confirmation: "Letmein1!"
- And I go to the admin login page
- And I try the password "wrong trousers" 5 times in a row for the email "admin%"
- And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "Letmein1!"
- And I press "Sign in"
- Then I should be on the admin home page
-
- Scenario: Login as a user who hasn't changed their password for over 9 months
- Given a moderator user exists with email: "admin@example.com", password: "Letmein1!", password_confirmation: "Letmein1!"
- And the password was changed 10 months ago
+ Scenario: Invalid login
+ Given an invalid SSO login
When I go to the admin login page
And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "Letmein1!"
And I press "Sign in"
- Then I should be on the admin edit profile page for "admin@example.com"
- And I should be connected to the server via an ssl connection
- And I fill in "Current password" with "Letmein1!"
- And I fill in "New password" with "Letmeout1!"
- And I fill in "Password confirmation" with "Letmeout1!"
- And I press "Save"
- Then I should be on the admin home page
+ Then I should see "There was a problem logging in - please contact support"
+ And should not see "Logout"
- Scenario: Login as a user who is logging in for the first time
- Given a moderator user exists with email: "admin@example.com", password: "Letmein1!", password_confirmation: "Letmein1!", force_password_reset: true
+ Scenario: Valid login but no role
+ Given a valid SSO login with no roles
When I go to the admin login page
- And I fill in "Email" with "admin@example.com"
- And I fill in "Password" with "Letmein1!"
+ And I fill in "Email" with "norole@example.com"
And I press "Sign in"
- Then I should be on the admin edit profile page for "admin@example.com"
- And I fill in "Current password" with "Letmein1!"
- And I fill in "New password" with "Letmeout1!"
- And I fill in "Password confirmation" with "Letmeout1!"
- And I press "Save"
- Then I should be on the admin home page
+ Then I should see "Invalid login details"
+ And should not see "Logout"
+
diff --git a/features/admin/admin_users.feature b/features/admin/admin_users.feature
new file mode 100644
index 000000000..de406a8b8
--- /dev/null
+++ b/features/admin/admin_users.feature
@@ -0,0 +1,35 @@
+@admin
+Feature: Admin users index and crud
+ As a sysadmin
+ I can see the admin users index and crud admin users
+
+ Background:
+ Given the time is "Sun, 17 Dec 2023 08:28:41 +0000"
+ And I am logged in as a sysadmin with the email "muddy@example.com", first_name "Sys", last_name "Admin"
+ And a moderator user exists with email: "naomi@example.com", first_name: "Naomi", last_name: "Campbell"
+
+ Scenario: Accessing the admin users index
+ When I go to the admin home page
+ And I follow "Users"
+ Then I should be on the admin users index page
+ And I should be connected to the server via an ssl connection
+
+ Scenario: Ordering of the users index
+ Given a moderator user exists with email: "derek@example.com", first_name: "Derek", last_name: "Jacobi", current_sign_in_at: "2023-12-11 14:27:14"
+ And a moderator user exists with email: "helen@example.com", first_name: "Helen", last_name: "Hunt", current_sign_in_at: "2023-12-15 10:09:31"
+ When I go to the admin users index page
+ Then I should see the following admin user table:
+ | Name | Email | Role | Last login |
+ | Admin, Sys | muddy@example.com | sysadmin | 08:28am on 17 December 2023 |
+ | Campbell, Naomi | naomi@example.com | moderator | |
+ | Hunt, Helen | helen@example.com | moderator | 10:09am on 15 December 2023 |
+ | Jacobi, Derek | derek@example.com | moderator | 14:27pm on 11 December 2023 |
+ And the markup should be valid
+
+ Scenario: Pagination of the users index
+ Given 50 moderator users exist
+ When I go to the admin users index page
+ And I follow "Next"
+ Then I should see 2 rows in the admin user table
+ And I follow "Previous"
+ Then I should see 50 rows in the admin user table
diff --git a/features/admin/admin_users_crud.feature b/features/admin/admin_users_crud.feature
deleted file mode 100644
index c3c79ac11..000000000
--- a/features/admin/admin_users_crud.feature
+++ /dev/null
@@ -1,98 +0,0 @@
-@admin
-Feature: Admin users index and crud
- As a sysadmin
- I can see the admin users index and crud admin users
-
- Background:
- Given I am logged in as a sysadmin with the email "muddy@fox.com", first_name "Sys", last_name "Admin"
- And a moderator user exists with email: "naomi@example.com", first_name: "Naomi", last_name: "Campbell"
-
- Scenario: Accessing the admin users index
- When I go to the admin home page
- And I follow "Users"
- Then I should be on the admin users index page
- And I should be connected to the server via an ssl connection
-
- Scenario: Ordering of the users index
- Given a moderator user exists with email: "derek@example.com", first_name: "Derek", last_name: "Jacobi"
- And a moderator user exists with email: "helen@example.com", first_name: "Helen", last_name: "Hunt", failed_login_count: 5
- When I go to the admin users index page
- Then I should see the following admin user table:
- | Name | Email | Role | Disabled |
- | Admin, Sys | muddy@fox.com | sysadmin | |
- | Campbell, Naomi | naomi@example.com | moderator | |
- | Hunt, Helen | helen@example.com | moderator | Yes |
- | Jacobi, Derek | derek@example.com | moderator | |
- And the markup should be valid
-
- Scenario: Pagination of the users index
- Given 50 moderator users exist
- When I go to the admin users index page
- And I follow "Next"
- Then I should see 2 rows in the admin user table
- And I follow "Previous"
- Then I should see 50 rows in the admin user table
-
- Scenario: Inspecting the new user form
- When I go to the admin users index page
- And I follow "New user"
- Then I should be on the admin new user page
- And I should be connected to the server via an ssl connection
- And I should see a "First name" text field
- And I should see a "Last name" text field
- And I should see a "Email" email field
- And I should see a "sysadmin" radio field
- And I should see a "moderator" radio field
- And I should see a "Force password reset" checkbox field
- And I should see a "Account disabled" checkbox field
- And I should see a "Password" password field
- And I should see a "Password confirmation" password field
-
- Scenario: Creating a new user
- When I go to the admin users index page
- And I follow "New user"
- And I fill in "First name" with "Derek"
- And I fill in "Last name" with "Jacobi"
- And I fill in "Email" with "derek@example.com"
- And I choose "sysadmin"
- And I fill in "Password" with "Letmein1!"
- And I fill in "Password confirmation" with "Letmein1!"
- And I press "Save"
- Then I should be on the admin users index page
- And I should see "derek@example.com"
- When I follow "Jacobi, Derek"
- Then the "sysadmin" radio button should be chosen
- And the "Account disabled" checkbox should not be checked
-
- Scenario: Updating a user
- When I go to the admin users index page
- And I follow "Campbell, Naomi"
- And I fill in "First name" with "Nolene"
- And I fill in "Email" with "helen@example.com"
- And I check "Account disabled"
- And I press "Save"
- Then I should be on the admin users index page
- And I should see "helen@example.com"
- When I follow "Campbell, Nolene"
- And the "Email" field should contain "helen@example.com"
- And the "Account disabled" checkbox should be checked
- And a moderator user should exist with email: "helen@example.com", failed_login_count: "5"
- And I should be connected to the server via an ssl connection
-
- Scenario: Enabling a user's disabled account
- Given a moderator user exists with email: "derek@example.com", first_name: "Derek", last_name: "Jacobi", failed_login_count: "5"
- When I go to the admin users index page
- And I follow "Jacobi, Derek"
- And the "Account disabled" checkbox should be checked
- And I uncheck "Account disabled"
- And I press "Save"
- Then I should be on the admin users index page
- When I follow "Jacobi, Derek"
- And the "Account disabled" checkbox should not be checked
- And a moderator user should exist with email: "derek@example.com", failed_login_count: "0"
-
- Scenario: Deleting a user
- When I go to the admin users index page
- And I follow "Delete" for "naomi@example.com"
- Then a admin user should not exist with email: "naomi@example.com"
- And the row with the name "naomi@example.com" is not listed
diff --git a/features/admin/all_petitions.feature b/features/admin/all_petitions.feature
index 65fabca5b..7b9e44ed6 100644
--- a/features/admin/all_petitions.feature
+++ b/features/admin/all_petitions.feature
@@ -108,19 +108,19 @@ Feature: A moderator user views all petitions
| My open petition with scheduled debate date |
Scenario: A sysadmin can view all petitions
- Given I am logged in as a sysadmin
+ Given I log out and login back in as a sysadmin
And an open petition exists with action: "Simply the best"
When I view all petitions
Then I should see "Simply the best"
Scenario: A moderator user can view all petitions
- Given I am logged in as a moderator
+ Given I log out and login back in as a moderator
And an open petition exists with action: "Simply the best"
When I view all petitions
Then I should see "Simply the best"
Scenario: A moderator can download petitions as CSV
- Given I am logged in as a moderator
+ Given I log out and login back in as a moderator
And the time is "2015-08-21 06:00:00"
When I view all petitions
And I follow "Download CSV"
diff --git a/features/admin/anonymize_petitions.feature b/features/admin/anonymize_petitions.feature
index 46fc4ad31..5457e8f16 100644
--- a/features/admin/anonymize_petitions.feature
+++ b/features/admin/anonymize_petitions.feature
@@ -4,7 +4,7 @@ Feature: An admin anonymizes petitions
I want to anonymize all petitions 6 months after parliament closes in accordance with our privacy policy
Background:
- Given I am logged in as a sysadmin with the email "muddy@fox.com", first_name "Sys", last_name "Admin"
+ Given I am logged in as a sysadmin with the email "muddy@example.com", first_name "Sys", last_name "Admin"
@javascript
Scenario: Admin anonymizes petitions 6 months after parliament closes
diff --git a/features/admin/profile.feature b/features/admin/profile.feature
deleted file mode 100644
index b9430d02c..000000000
--- a/features/admin/profile.feature
+++ /dev/null
@@ -1,42 +0,0 @@
-@admin
-Feature: Moderator users password change
- As a an moderator
- I can change my password
-
- Background:
- Given I am logged in as a moderator named "John Moderator" with the password "Letmein1!"
-
- Scenario: Accessing the Profile page
- When I go to the admin home page
- And I follow "John Moderator"
- Then I should be on the admin edit profile page
- And I should see a "Current password" password field
- And I should see a "New password" password field
- And I should see a "Password confirmation" password field
- And I should be connected to the server via an ssl connection
- And the markup should be valid
-
- Scenario: Changing password successfully
- When I go to the admin edit profile page
- And I fill in "Current password" with "Letmein1!"
- And I fill in "New password" with "Letmeout1!"
- And I fill in "Password confirmation" with "Letmeout1!"
- And I press "Save"
- Then I should be on the admin home page
- And I should see "Password was successfully updated"
-
- Scenario: Incorrect current password
- When I go to the admin edit profile page
- And I fill in "Current password" with "wrong password"
- And I fill in "New password" with "Letmeout1!"
- And I fill in "Password confirmation" with "Letmeout1!"
- And I press "Save"
- Then I should see "Current password is incorrect"
-
- Scenario: Invalid new password
- When I go to the admin edit profile page
- And I fill in "Current password" with "Letmein1!"
- And I fill in "New password" with "12345678"
- And I fill in "Password confirmation" with "12345678"
- And I press "Save"
- Then I should see "Password must contain at least one digit, a lower and upper case letter and a special character"
diff --git a/features/step_definitions/authentication_steps.rb b/features/step_definitions/authentication_steps.rb
index e3cdbd1c5..fc0cf7299 100644
--- a/features/step_definitions/authentication_steps.rb
+++ b/features/step_definitions/authentication_steps.rb
@@ -1,44 +1,76 @@
Given /^I am logged in as a sysadmin$/ do
- @user = FactoryBot.create(:sysadmin_user)
+ @user = FactoryBot.build(:sysadmin_sso_user)
step "the admin user is logged in"
end
Given /^I am logged in as a moderator$/ do
- @user = FactoryBot.create(:moderator_user)
+ @user = FactoryBot.build(:moderator_sso_user)
step "the admin user is logged in"
end
-Given /^I am logged in as a moderator named "([^\"]*)"$/ do |name|
- first_name, last_name = name.split
- @user = FactoryBot.create(:moderator_user, first_name: first_name, last_name: last_name)
- step "the admin user is logged in"
+Given(/^I log out and login back in as a sysadmin$/) do
+ @user = FactoryBot.build(:sysadmin_sso_user)
+
+ steps %q[
+ the admin user is logged out
+ the admin user is logged in
+ ]
end
-Given /^I am logged in as a moderator with the password "([^\"]*)"$/ do |password|
- @user = FactoryBot.create(:moderator_user, :password => password, :password_confirmation => password)
- step "the admin user is logged in"
+Given(/^I log out and login back in as a moderator$/) do
+ @user = FactoryBot.build(:moderator_sso_user)
+
+ steps %q[
+ the admin user is logged out
+ the admin user is logged in
+ ]
end
-Given /^I am logged in as a moderator named "([^\"]*)" with the password "([^\"]*)"$/ do |name, password|
+Given /^I am logged in as a moderator named "([^\"]*)"$/ do |name|
first_name, last_name = name.split
- @user = FactoryBot.create(:moderator_user, first_name: first_name, last_name: last_name, :password => password, :password_confirmation => password)
+ @user = FactoryBot.build(:moderator_sso_user, first_name: first_name, last_name: last_name)
step "the admin user is logged in"
end
Given /^I am logged in as a sysadmin named "([^\"]*)"$/ do |name|
first_name, last_name = name.split
- @user = FactoryBot.create(:sysadmin_user, first_name: first_name, last_name: last_name)
+ @user = FactoryBot.build(:sysadmin_sso_user, first_name: first_name, last_name: last_name)
step "the admin user is logged in"
end
Given /^I am logged in as a sysadmin with the email "([^\"]*)", first_name "([^\"]*)", last_name "([^\"]*)"$/ do |email, first_name, last_name|
- @user = FactoryBot.create(:sysadmin_user, :email => email, :first_name => first_name, :last_name => last_name)
+ @user = FactoryBot.build(:sysadmin_sso_user, email: email, first_name: first_name, last_name: last_name)
step "the admin user is logged in"
end
Given /^the admin user is logged in$/ do
+ OmniAuth.config.mock_auth[:example] = @user
+
visit admin_login_url
- fill_in("Email", :with => @user.email)
- fill_in("Password", :with => "Letmein1!")
+ fill_in("Email", with: @user.uid)
click_button("Sign in")
end
+
+Given /^the admin user is logged out$/ do
+ visit admin_logout_url
+end
+
+Given /^a sysadmin SSO user exists$/ do
+ OmniAuth.config.mock_auth[:example] = FactoryBot.build(:sysadmin_sso_user)
+end
+
+Given /^a moderator SSO user exists$/ do
+ OmniAuth.config.mock_auth[:example] = FactoryBot.build(:moderator_sso_user)
+end
+
+Given /^a reviewer SSO user exists$/ do
+ OmniAuth.config.mock_auth[:example] = FactoryBot.build(:reviewer_sso_user)
+end
+
+Given(/^a valid SSO login with no roles$/) do
+ OmniAuth.config.mock_auth[:example] = FactoryBot.build(:norole_sso_user)
+end
+
+Given /^an invalid SSO login$/ do
+ OmniAuth.config.mock_auth[:example] = :invalid_credentials
+end
diff --git a/features/step_definitions/pickle_steps.rb b/features/step_definitions/pickle_steps.rb
index c7d58b89b..f8ee725a9 100644
--- a/features/step_definitions/pickle_steps.rb
+++ b/features/step_definitions/pickle_steps.rb
@@ -1,25 +1,13 @@
-Given(/^a sysadmin user exists with first_name: "([^"]*)", last_name: "([^"]*)", email: "([^"]*)", password: "([^"]*)", password_confirmation: "([^"]*)"$/) do |first_name, last_name, email, password, password_confirmation|
- @user = FactoryBot.create(:sysadmin_user, first_name: first_name, last_name: last_name, email: email, password: password, password_confirmation: password_confirmation)
-end
-
-Given(/^a moderator user exists with first_name: "([^"]*)", last_name: "([^"]*)", email: "([^"]*)", password: "([^"]*)", password_confirmation: "([^"]*)"$/) do |first_name, last_name, email, password, password_confirmation|
- @user = FactoryBot.create(:moderator_user, first_name: first_name, last_name: last_name, email: email, password: password, password_confirmation: password_confirmation)
-end
-
-Given(/^a moderator user exists with email: "([^"]*)", password: "([^"]*)", password_confirmation: "([^"]*)"$/) do |email, password, password_confirmation|
- @user = FactoryBot.create(:moderator_user, email: email, password: password, password_confirmation: password_confirmation)
-end
-
-Given(/^a moderator user exists with email: "([^"]*)", password: "([^"]*)", password_confirmation: "([^"]*)", force_password_reset: true$/) do |email, password, password_confirmation|
- @user = FactoryBot.create(:moderator_user, email: email, password: password, password_confirmation: password_confirmation, force_password_reset: true)
+Given(/^a sysadmin user exists with email: "([^"]*)", first_name: "([^"]*)", last_name: "([^"]*)"$/) do |email, first_name, last_name|
+ @user = FactoryBot.create(:sysadmin_user, first_name: first_name, last_name: last_name, email: email)
end
Given(/^a moderator user exists with email: "([^"]*)", first_name: "([^"]*)", last_name: "([^"]*)"$/) do |email, first_name, last_name|
@user = FactoryBot.create(:moderator_user, first_name: first_name, last_name: last_name, email: email)
end
-Given(/^a moderator user exists with email: "([^"]*)", first_name: "([^"]*)", last_name: "([^"]*)", failed_login_count: (\d+)$/) do |email, first_name, last_name, failed_login_count|
- @user = FactoryBot.create(:moderator_user, first_name: first_name, last_name: last_name, email: email, failed_login_count: failed_login_count)
+Given(/^a moderator user exists with email: "([^"]*)", first_name: "([^"]*)", last_name: "([^"]*)", current_sign_in_at: "([^"]*)"$/) do |email, first_name, last_name, current_sign_in_at|
+ @user = FactoryBot.create(:moderator_user, first_name: first_name, last_name: last_name, email: email, current_sign_in_at: current_sign_in_at)
end
Given(/^(\d+) moderator users exist$/) do |number|
@@ -34,14 +22,6 @@
end
end
-When(/^a moderator user should exist with email: "([^"]*)", failed_login_count: "([^"]*)"$/) do |email, failed_login_count|
- expect(AdminUser.where(email: email, failed_login_count: failed_login_count)).to exist
-end
-
-Given(/^a moderator user exists with email: "([^"]*)", first_name: "([^"]*)", last_name: "([^"]*)", failed_login_count: "([^"]*)"$/) do |email, first_name, last_name, failed_login_count|
- @user = FactoryBot.create(:moderator_user, email: email, first_name: first_name, last_name: last_name, failed_login_count: failed_login_count)
-end
-
Then(/^a admin user should not exist with email: "([^"]*)"$/) do |email|
expect(AdminUser.where(email: email)).not_to exist
end
diff --git a/features/support/hooks.rb b/features/support/hooks.rb
index 103d1aadf..1d3405adc 100644
--- a/features/support/hooks.rb
+++ b/features/support/hooks.rb
@@ -83,3 +83,16 @@
Before do
Rails.application.env_config['action_dispatch.show_detailed_exceptions'] = false
end
+
+Before do
+ OmniAuth.config.test_mode = true
+
+ OmniAuth.config.on_failure = Proc.new { |env|
+ OmniAuth::FailureEndpoint.new(env).redirect_to_failure
+ }
+end
+
+After do
+ OmniAuth.config.mock_auth[:example] = nil
+ OmniAuth.config.test_mode = false
+end
diff --git a/features/support/paths.rb b/features/support/paths.rb
index 250e0f80c..10e44ef57 100644
--- a/features/support/paths.rb
+++ b/features/support/paths.rb
@@ -110,12 +110,6 @@ def admin_url(admin_page)
when /^new user page$/
new_admin_admin_user_url
- when /^edit profile page$/
- edit_admin_profile_url(@user)
-
- when /^edit profile page for "([^\"]*)"$/
- edit_admin_profile_url(AdminUser.find_by(email: $1))
-
when /^debate outcomes form page for "([^\"]*)"$/
admin_petition_debate_outcome_url(Petition.find_by(action: $1))
diff --git a/lib/package_builder/scripts/after_install.sh b/lib/package_builder/scripts/after_install.sh
index edf20d66e..2e3d18ebc 100644
--- a/lib/package_builder/scripts/after_install.sh
+++ b/lib/package_builder/scripts/after_install.sh
@@ -5,6 +5,7 @@ set -o pipefail
chown -R deploy:deploy /home/deploy/epetitions/releases/<%= release %>
su - deploy <<'EOF'
+ln -nfs /home/deploy/epetitions/shared/config/sso.yml /home/deploy/epetitions/releases/<%= release %>/config/sso.yml
ln -nfs /home/deploy/epetitions/shared/tmp /home/deploy/epetitions/releases/<%= release %>/tmp
ln -nfs /home/deploy/epetitions/shared/log /home/deploy/epetitions/releases/<%= release %>/log
ln -nfs /home/deploy/epetitions/shared/bundle /home/deploy/epetitions/releases/<%= release %>/vendor/bundle
diff --git a/lib/tasks/epets.rake b/lib/tasks/epets.rake
index 79ae580b6..c0457e1be 100644
--- a/lib/tasks/epets.rake
+++ b/lib/tasks/epets.rake
@@ -6,7 +6,6 @@ namespace :epets do
if AdminUser.find_by(email: 'admin@example.com').nil?
admin = AdminUser.new(:first_name => 'Cool', :last_name => 'Admin', :email => 'admin@example.com')
admin.role = 'sysadmin'
- admin.password = admin.password_confirmation = 'Letmein1!'
admin.save!
end
end
diff --git a/spec/controllers/admin/admin_users_controller_spec.rb b/spec/controllers/admin/admin_users_controller_spec.rb
index f65cae87a..de055aec5 100644
--- a/spec/controllers/admin/admin_users_controller_spec.rb
+++ b/spec/controllers/admin/admin_users_controller_spec.rb
@@ -8,13 +8,6 @@
expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/login")
end
end
-
- describe "GET 'new'" do
- it "should redirect to the login page" do
- get :new
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/login")
- end
- end
end
context "logged in as moderator user" do
@@ -31,19 +24,6 @@
end
end
- context "logged in as sysadmin but need to reset password" do
- let(:user) { FactoryBot.create(:sysadmin_user, :force_password_reset => true) }
- before :each do
- login_as(user)
- end
-
- it "should redirect to edit profile page" do
- expect(user.has_to_change_password?).to be_truthy
- get :index
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
describe "logged in as sysadmin user" do
let(:user) { FactoryBot.create(:sysadmin_user, :first_name => 'Sys', :last_name => 'Admin') }
before :each do
@@ -68,160 +48,5 @@
expect(assigns[:users]).to eq([user, @user4, @user2, @user1, @user3])
end
end
-
- describe "GET 'new'" do
- it "should be successful" do
- get :new
- expect(response).to be_successful
- end
-
- it "should assign a new user" do
- get :new
- expect(assigns[:user]).to be_a(AdminUser)
- expect(assigns[:user]).to be_new_record
- end
- end
-
- describe "POST 'create'" do
- def do_create
- post :create, params: { admin_user: admin_user_attributes }
- end
-
- describe "with valid params" do
- let(:admin_user_attributes) do
- {
- :first_name => 'John',
- :last_name => 'Kennedy',
- :role => 'moderator',
- :email => 'admin@example.com',
- :password => 'Letmein1!',
- :password_confirmation => 'Letmein1!'
- }
- end
-
- it "should create a new user" do
- expect do
- do_create
- end.to change(AdminUser, :count).by(1)
- end
-
- it "should redirect to the index" do
- do_create
- expect(response).to redirect_to(:action => :index)
- end
- end
-
- describe "with invalid params" do
- let(:admin_user_attributes) do
- {
- :email => 'admin@example.com'
- }
- end
-
- it "should not create a new user" do
- expect {
- do_create
- }.not_to change(AdminUser, :count)
- end
-
- it "should re-render the new template" do
- do_create
- expect(response).to render_template('new')
- end
- end
- end
-
- describe "GET 'edit'" do
- let(:edit_user) { FactoryBot.create(:moderator_user) }
-
- def do_get
- get :edit, params: { id: edit_user.to_param }
- end
-
- it "should be successful" do
- do_get
- expect(response).to be_successful
- end
-
- it "should assign the user" do
- do_get
- expect(assigns[:user]).to be_a(AdminUser)
- expect(assigns[:user]).to eq edit_user
- end
- end
-
- describe "PUT 'update'" do
- let(:edit_user) { FactoryBot.create(:moderator_user, :email => "admin@example.com", :failed_login_count => 5) }
-
- def do_update
- patch :update, params: {
- id: edit_user.to_param,
- admin_user: admin_user_attributes
- }
- end
-
- describe "with valid params" do
- let(:admin_user_attributes) do
- {
- :email => "another_admin@example.com",
- :account_disabled => '0'
- }
- end
-
- it "should update the user and redirect to the index" do
- do_update
- edit_user.reload
- expect(edit_user.email).to eq("another_admin@example.com")
- expect(response).to redirect_to(:action => :index)
- end
-
- it "should reset the failed login count to 0" do
- do_update
- edit_user.reload
- expect(edit_user.failed_login_count).to eq(0)
- end
- end
-
- describe "with invalid params" do
- let(:admin_user_attributes) do
- {
- :email => "bademailaddress"
- }
- end
-
- it "should not update the user" do
- do_update
- edit_user.reload
- expect(edit_user.email).to eq("admin@example.com")
- expect(response).to render_template('edit')
- end
- end
- end
-
- describe "DELETE 'destroy'" do
- let(:delete_user) { FactoryBot.create(:moderator_user, :email => 'admin@example.com') }
-
- it "deletes the requested user" do
- delete :destroy, params: { id: delete_user.to_param }
- expect(AdminUser.exists?(delete_user.id)).to be_falsey
- expect(response).to redirect_to(:action => :index)
- end
-
- it "will not let you delete yourself" do
- delete :destroy, params: { id: user.to_param }
- expect(AdminUser.exists?(user.id)).to be_truthy
- expect(response).to redirect_to(:action => :index)
- expect(flash[:alert]).to eq "You are not allowed to delete yourself!"
- end
-
- it "will not let you delete users that have moderated petitions" do
- petition = FactoryBot.create(:open_petition, moderated_by: delete_user)
-
- delete :destroy, params: { id: delete_user.to_param }
- expect(AdminUser.exists?(user.id)).to be_truthy
- expect(response).to redirect_to(:action => :index)
- expect(flash[:alert]).to eq "The user has moderated petitions so you can only deactivate their account"
- end
- end
end
end
diff --git a/spec/controllers/admin/archived/debate_outcomes_controller_spec.rb b/spec/controllers/admin/archived/debate_outcomes_controller_spec.rb
index 5fa0ec367..01eeb1583 100644
--- a/spec/controllers/admin/archived/debate_outcomes_controller_spec.rb
+++ b/spec/controllers/admin/archived/debate_outcomes_controller_spec.rb
@@ -41,25 +41,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/government_response_controller_spec.rb b/spec/controllers/admin/archived/government_response_controller_spec.rb
index 7725d89a4..decfeca3a 100644
--- a/spec/controllers/admin/archived/government_response_controller_spec.rb
+++ b/spec/controllers/admin/archived/government_response_controller_spec.rb
@@ -42,25 +42,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
@@ -610,5 +591,78 @@ def do_patch(overrides = {})
end
end
end
+
+ describe 'DELETE /destroy' do
+ before do
+ expect(Archived::Petition).to receive_message_chain(:moderated, :find).with(petition.to_param).and_return(petition)
+ end
+
+ context "when the petition has a government response" do
+ let!(:petition) { FactoryBot.create(:archived_petition, :response) }
+
+ before do
+ expect(petition).to receive(:government_response).and_return(government_response)
+ end
+
+ context "when the government response is succcessfully deleted" do
+ it "redirects to the petition page and displays a notice" do
+ delete :destroy, params: { petition_id: petition.id }
+
+ expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/archived/petitions/#{petition.id}")
+ expect(controller).to set_flash[:notice].to("Deleted government response successfully")
+ end
+
+ it "updates the email_requested_at timestamp for 'government_response'" do
+ email_requested_at = 1.hour.ago
+ petition.set_email_requested_at_for("government_response", to: email_requested_at)
+
+ expect {
+ delete :destroy, params: { petition_id: petition.id }
+ }.to change {
+ petition.get_email_requested_at_for("government_response")
+ }.from(email_requested_at).to(be_within(1.second).of(Time.current))
+ end
+
+ it "updates the government_response_at timestamp to be nil" do
+ expect {
+ delete :destroy, params: { petition_id: petition.id }
+ }.to change {
+ petition.government_response_at
+ }.from(be_present).to(be_nil)
+ end
+ end
+
+ context "when the government response is not successfully deleted" do
+ before do
+ expect(government_response).to receive(:destroy).and_return(false)
+ expect(government_response).to receive(:destroyed?).and_return(false)
+ end
+
+ it "redirects to the government response page and displays an alert" do
+ delete :destroy, params: { petition_id: petition.id }
+
+ expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/archived/petitions/#{petition.id}/government-response")
+ expect(controller).to set_flash[:alert].to("Unable to delete government response - please contact support")
+ end
+ end
+ end
+
+ context "when the petition has no government response" do
+ let!(:petition) { FactoryBot.create(:archived_petition) }
+ let(:new_government_response) { petition.build_government_response }
+
+ before do
+ expect(petition).to receive(:government_response).and_return(nil)
+ expect(petition).to receive(:build_government_response).and_return(new_government_response)
+ end
+
+ it "redirects to the petition page and displays a notice" do
+ delete :destroy, params: { petition_id: petition.id }
+
+ expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/archived/petitions/#{petition.id}")
+ expect(controller).to set_flash[:notice].to("Deleted government response successfully")
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/admin/archived/locks_controller_spec.rb b/spec/controllers/admin/archived/locks_controller_spec.rb
index 06c630e4c..54500efd7 100644
--- a/spec/controllers/admin/archived/locks_controller_spec.rb
+++ b/spec/controllers/admin/archived/locks_controller_spec.rb
@@ -20,28 +20,6 @@
end
end
- context "when logged in as a moderator requiring a password reset" do
- let(:moderator) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(moderator) }
-
- [
- ["GET", "/admin/archived/petitions/:petition_id/lock.json", :show, { petition_id: "100000" }],
- ["POST", "/admin/archived/petitions/:petition_id/lock.json", :create, { petition_id: "100000" }],
- ["PATCH", "/admin/archived/petitions/:petition_id/lock.json", :update, { petition_id: "100000" }],
- ["DELETE", "/admin/archived/petitions/:petition_id/lock.json", :destroy, { petition_id: "100000" }]
- ].each do |method, path, action, params|
-
- describe "#{method} #{path}" do
- before { process action, method: method, params: params, format: :json }
-
- it "redirects to the admin profile page" do
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{moderator.id}/edit")
- end
- end
-
- end
- end
-
context "when logged in as a moderator" do
let(:moderator) { FactoryBot.create(:moderator_user) }
let(:petition) { FactoryBot.create(:archived_petition) }
diff --git a/spec/controllers/admin/archived/notes_controller_spec.rb b/spec/controllers/admin/archived/notes_controller_spec.rb
index c6cbb0057..3a9210851 100644
--- a/spec/controllers/admin/archived/notes_controller_spec.rb
+++ b/spec/controllers/admin/archived/notes_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/petition_departments_controller_spec.rb b/spec/controllers/admin/archived/petition_departments_controller_spec.rb
index d846b1df7..f0fc1f629 100644
--- a/spec/controllers/admin/archived/petition_departments_controller_spec.rb
+++ b/spec/controllers/admin/archived/petition_departments_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/archived/petitions/:petition_id/departments" do
- it "redirects to the edit profile page" do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "PATCH /admin/archived/petitions/:petition_id/departments" do
- it "redirects to the edit profile page" do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/petition_details_controller_spec.rb b/spec/controllers/admin/archived/petition_details_controller_spec.rb
index d25704269..62fbd9243 100644
--- a/spec/controllers/admin/archived/petition_details_controller_spec.rb
+++ b/spec/controllers/admin/archived/petition_details_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET #show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH #update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context 'logged in as moderator user' do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/petition_emails_controller_spec.rb b/spec/controllers/admin/archived/petition_emails_controller_spec.rb
index 18f1b8830..ffb892f1e 100644
--- a/spec/controllers/admin/archived/petition_emails_controller_spec.rb
+++ b/spec/controllers/admin/archived/petition_emails_controller_spec.rb
@@ -90,48 +90,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:email) { FactoryBot.create(:archived_petition_email, petition: petition) }
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
-
- before { login_as(user) }
-
- describe 'GET /new' do
- it 'redirects to edit profile page' do
- get :new, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'POST /' do
- it 'redirects to edit profile page' do
- post :create, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'GET /:id/edit' do
- it 'redirects to the login page' do
- get :edit, params: { petition_id: petition.id, id: email.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /:id' do
- it 'redirects to the login page' do
- patch :update, params: { petition_id: petition.id, id: email.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'DELETE /:id' do
- it 'redirects to the login page' do
- patch :destroy, params: { petition_id: petition.id, id: email.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/petition_tags_controller_spec.rb b/spec/controllers/admin/archived/petition_tags_controller_spec.rb
index 052df7696..bb7f9f17c 100644
--- a/spec/controllers/admin/archived/petition_tags_controller_spec.rb
+++ b/spec/controllers/admin/archived/petition_tags_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/archived/petitions/:petition_id/tags" do
- it "redirects to the edit profile page" do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "PATCH /admin/archived/petitions/:petition_id/tags" do
- it "redirects to the edit profile page" do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/petition_topics_controller_spec.rb b/spec/controllers/admin/archived/petition_topics_controller_spec.rb
index ef1763f61..dd04af12f 100644
--- a/spec/controllers/admin/archived/petition_topics_controller_spec.rb
+++ b/spec/controllers/admin/archived/petition_topics_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/archived/petitions/:petition_id/topics" do
- it "redirects to the edit profile page" do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "PATCH /admin/archived/petitions/:petition_id/topics" do
- it "redirects to the edit profile page" do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/petitions_controller_spec.rb b/spec/controllers/admin/archived/petitions_controller_spec.rb
index bf3f7a527..44727edaf 100644
--- a/spec/controllers/admin/archived/petitions_controller_spec.rb
+++ b/spec/controllers/admin/archived/petitions_controller_spec.rb
@@ -21,25 +21,6 @@
end
end
- context "when logged in as a moderator but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/archived/petitions" do
- it "redirects to the edit profile page" do
- get :index
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "GET /admin/archived/petitions/:id" do
- it "redirects to the edit profile page" do
- get :show, params: { id: "100000" }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as a moderator" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/schedule_debate_controller_spec.rb b/spec/controllers/admin/archived/schedule_debate_controller_spec.rb
index 03599b847..a7b9ad8fa 100644
--- a/spec/controllers/admin/archived/schedule_debate_controller_spec.rb
+++ b/spec/controllers/admin/archived/schedule_debate_controller_spec.rb
@@ -41,25 +41,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/archived/signatures_controller_spec.rb b/spec/controllers/admin/archived/signatures_controller_spec.rb
index f0ced78f7..0543c445b 100644
--- a/spec/controllers/admin/archived/signatures_controller_spec.rb
+++ b/spec/controllers/admin/archived/signatures_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context "logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/archived/signatures" do
- it "redirects to the login page" do
- get :index, params: { q: "Alice" }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "DELETE /admin/archived/signatures/:id" do
- it "redirects to edit profile page" do
- delete :destroy, params: { id: signature.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
let(:token) { SecureRandom.base64(32) }
diff --git a/spec/controllers/admin/debate_outcomes_controller_spec.rb b/spec/controllers/admin/debate_outcomes_controller_spec.rb
index 985e988f2..aa1f8d518 100644
--- a/spec/controllers/admin/debate_outcomes_controller_spec.rb
+++ b/spec/controllers/admin/debate_outcomes_controller_spec.rb
@@ -40,25 +40,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/government_response_controller_spec.rb b/spec/controllers/admin/government_response_controller_spec.rb
index 978bfeb8b..344ee3f58 100644
--- a/spec/controllers/admin/government_response_controller_spec.rb
+++ b/spec/controllers/admin/government_response_controller_spec.rb
@@ -41,25 +41,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
@@ -642,5 +623,82 @@ def do_patch(overrides = {})
end
end
end
+
+ describe 'DELETE /destroy' do
+ before do
+ expect(Petition).to receive_message_chain(:moderated, :find).with(petition.to_param).and_return(petition)
+ end
+
+ context "when the petition has a government response" do
+ let!(:petition) { FactoryBot.create(:responded_petition) }
+
+ before do
+ expect(petition).to receive(:government_response).and_return(government_response)
+ end
+
+ context "when the government response is succcessfully deleted" do
+ before do
+ expect(Appsignal).to receive(:increment_counter).with("petition.responded", -1)
+ end
+
+ it "redirects to the petition page and displays a notice" do
+ delete :destroy, params: { petition_id: petition.id }
+
+ expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/petitions/#{petition.id}")
+ expect(controller).to set_flash[:notice].to("Deleted government response successfully")
+ end
+
+ it "updates the email_requested_at timestamp for 'government_response'" do
+ email_requested_at = 1.hour.ago
+ petition.set_email_requested_at_for("government_response", to: email_requested_at)
+
+ expect {
+ delete :destroy, params: { petition_id: petition.id }
+ }.to change {
+ petition.get_email_requested_at_for("government_response")
+ }.from(email_requested_at).to(be_within(1.second).of(Time.current))
+ end
+
+ it "updates the government_response_at timestamp to be nil" do
+ expect {
+ delete :destroy, params: { petition_id: petition.id }
+ }.to change {
+ petition.government_response_at
+ }.from(be_present).to(be_nil)
+ end
+ end
+
+ context "when the government response is not successfully deleted" do
+ before do
+ expect(government_response).to receive(:destroy).and_return(false)
+ expect(government_response).to receive(:destroyed?).and_return(false)
+ end
+
+ it "redirects to the government response page and displays an alert" do
+ delete :destroy, params: { petition_id: petition.id }
+
+ expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/petitions/#{petition.id}/government-response")
+ expect(controller).to set_flash[:alert].to("Unable to delete government response - please contact support")
+ end
+ end
+ end
+
+ context "when the petition has no government response" do
+ let!(:petition) { FactoryBot.create(:open_petition) }
+ let(:new_government_response) { petition.build_government_response }
+
+ before do
+ expect(petition).to receive(:government_response).and_return(nil)
+ expect(petition).to receive(:build_government_response).and_return(new_government_response)
+ end
+
+ it "redirects to the petition page and displays a notice" do
+ delete :destroy, params: { petition_id: petition.id }
+
+ expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/petitions/#{petition.id}")
+ expect(controller).to set_flash[:notice].to("Deleted government response successfully")
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/admin/locks_controller_spec.rb b/spec/controllers/admin/locks_controller_spec.rb
index f6f12ac79..44289725c 100644
--- a/spec/controllers/admin/locks_controller_spec.rb
+++ b/spec/controllers/admin/locks_controller_spec.rb
@@ -20,28 +20,6 @@
end
end
- context "when logged in as a moderator requiring a password reset" do
- let(:moderator) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(moderator) }
-
- [
- ["GET", "/admin/petitions/:petition_id/lock.json", :show, { petition_id: "100000" }],
- ["POST", "/admin/petitions/:petition_id/lock.json", :create, { petition_id: "100000" }],
- ["PATCH", "/admin/petitions/:petition_id/lock.json", :update, { petition_id: "100000" }],
- ["DELETE", "/admin/petitions/:petition_id/lock.json", :destroy, { petition_id: "100000" }]
- ].each do |method, path, action, params|
-
- describe "#{method} #{path}" do
- before { process action, method: method, params: params, format: :json }
-
- it "redirects to the admin profile page" do
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{moderator.id}/edit")
- end
- end
-
- end
- end
-
context "when logged in as a moderator" do
let(:moderator) { FactoryBot.create(:moderator_user) }
let(:petition) { FactoryBot.create(:petition) }
diff --git a/spec/controllers/admin/moderation_delays_controller_spec.rb b/spec/controllers/admin/moderation_delays_controller_spec.rb
index a4adb43b7..94157bc54 100644
--- a/spec/controllers/admin/moderation_delays_controller_spec.rb
+++ b/spec/controllers/admin/moderation_delays_controller_spec.rb
@@ -18,26 +18,6 @@
end
end
- context "when logged in as a moderator requiring a password reset" do
- let(:moderator) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(moderator) }
-
- [
- ["GET", "/admin/moderation-delay/new", :new, {}],
- ["POST", "/admin/moderation-delay", :create, {}]
- ].each do |method, path, action, params|
-
- describe "#{method} #{path}" do
- before { process action, method: method, params: params }
-
- it "redirects to the admin profile page" do
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{moderator.id}/edit")
- end
- end
-
- end
- end
-
context "when logged in as a moderator" do
let(:moderator) { FactoryBot.create(:moderator_user) }
before { login_as(moderator) }
diff --git a/spec/controllers/admin/notes_controller_spec.rb b/spec/controllers/admin/notes_controller_spec.rb
index d15492bde..0000fce9a 100644
--- a/spec/controllers/admin/notes_controller_spec.rb
+++ b/spec/controllers/admin/notes_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/petition_departments_controller_spec.rb b/spec/controllers/admin/petition_departments_controller_spec.rb
index c85b0ac3d..bd6ddfd81 100644
--- a/spec/controllers/admin/petition_departments_controller_spec.rb
+++ b/spec/controllers/admin/petition_departments_controller_spec.rb
@@ -19,25 +19,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/petitions/:petition_id/departments" do
- it "redirects to the edit profile page" do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "PATCH /admin/petitions/:petition_id/departments" do
- it "redirects to the edit profile page" do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/petition_details_controller_spec.rb b/spec/controllers/admin/petition_details_controller_spec.rb
index d6578ff47..9444b088c 100644
--- a/spec/controllers/admin/petition_details_controller_spec.rb
+++ b/spec/controllers/admin/petition_details_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET #show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH #update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context 'logged in as moderator user' do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/petition_emails_controller_spec.rb b/spec/controllers/admin/petition_emails_controller_spec.rb
index e171426af..4335d2d76 100644
--- a/spec/controllers/admin/petition_emails_controller_spec.rb
+++ b/spec/controllers/admin/petition_emails_controller_spec.rb
@@ -89,48 +89,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:email) { FactoryBot.create(:petition_email, petition: petition) }
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
-
- before { login_as(user) }
-
- describe 'GET /new' do
- it 'redirects to edit profile page' do
- get :new, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'POST /' do
- it 'redirects to edit profile page' do
- post :create, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'GET /:id/edit' do
- it 'redirects to the login page' do
- get :edit, params: { petition_id: petition.id, id: email.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /:id' do
- it 'redirects to the login page' do
- patch :update, params: { petition_id: petition.id, id: email.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'DELETE /:id' do
- it 'redirects to the login page' do
- patch :destroy, params: { petition_id: petition.id, id: email.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/petition_tags_controller_spec.rb b/spec/controllers/admin/petition_tags_controller_spec.rb
index 709a78741..46f8acbd9 100644
--- a/spec/controllers/admin/petition_tags_controller_spec.rb
+++ b/spec/controllers/admin/petition_tags_controller_spec.rb
@@ -19,25 +19,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/petitions/:petition_id/tags" do
- it "redirects to the edit profile page" do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "PATCH /admin/petitions/:petition_id/tags" do
- it "redirects to the edit profile page" do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/petition_topics_controller_spec.rb b/spec/controllers/admin/petition_topics_controller_spec.rb
index 9c5c39cb0..cedc49a75 100644
--- a/spec/controllers/admin/petition_topics_controller_spec.rb
+++ b/spec/controllers/admin/petition_topics_controller_spec.rb
@@ -19,25 +19,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/petitions/:petition_id/topics" do
- it "redirects to the edit profile page" do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "PATCH /admin/petitions/:petition_id/topics" do
- it "redirects to the edit profile page" do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/petitions_controller_spec.rb b/spec/controllers/admin/petitions_controller_spec.rb
index 0e56c7249..8fd4ba438 100644
--- a/spec/controllers/admin/petitions_controller_spec.rb
+++ b/spec/controllers/admin/petitions_controller_spec.rb
@@ -17,25 +17,6 @@
end
end
- context "when logged in as a moderator but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/petitions" do
- it "redirects to the edit profile page" do
- get :index
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "GET /admin/petitions/:id" do
- it "redirects to the edit profile page" do
- get :show, params: { id: "100000" }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as a moderator" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/profile_controller_spec.rb b/spec/controllers/admin/profile_controller_spec.rb
deleted file mode 100644
index 2cdd7d488..000000000
--- a/spec/controllers/admin/profile_controller_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Admin::ProfileController, type: :controller, admin: true do
- before :each do
- @user = FactoryBot.create(:sysadmin_user, :password => 'Letmein1!',
- :password_confirmation => 'Letmein1!', :force_password_reset => true)
- end
-
- context "not logged in" do
- describe "GET 'edit'" do
- it "should redirect to the login page" do
- get 'edit', params: { id: @user.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/login")
- end
- end
- end
-
- context "logged in but need to reset password" do
- before :each do
- login_as(@user)
- end
-
- it "should render successfully" do
- expect(@user.has_to_change_password?).to be_truthy
- get :edit, params: { id: 50000 }
- expect(response).to be_successful
- end
- end
-
- context "logged in" do
- before :each do
- login_as(@user)
- end
-
- describe "GET 'edit'" do
- it "should render successfully" do
- get :edit, params: { id: 50000 }
- expect(response).to be_successful
- end
- end
-
- describe "GET 'update'" do
- def do_patch(current_password, new_password, password_confirmation)
- admin_user_attributes = {
- current_password: current_password,
- password: new_password,
- password_confirmation: password_confirmation
- }
- patch :update, params: { id: 50000, admin_user: admin_user_attributes }
- end
-
- context "successful password change" do
- it "should update password" do
- do_patch('Letmein1!', 'Letmeout1!', 'Letmeout1!')
- @user.reload
- expect(@user.valid_password?('Letmeout1!')).to be_truthy
- end
-
- it "should update password_changed_at to current time" do
- do_patch('Letmein1!', 'Letmeout1!', 'Letmeout1!')
- @user.reload
- expect(@user.password_changed_at).to be_within(1.second).of(Time.current)
- end
-
- it "should set force_password_reset to false" do
- expect(@user.force_password_reset).to be_truthy
- do_patch('Letmein1!', 'Letmeout1!', 'Letmeout1!')
- @user.reload
- expect(@user.force_password_reset).to be_falsey
- end
-
- it "should redirect" do
- do_patch('Letmein1!', 'Letmeout1!', 'Letmeout1!')
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin")
- end
- end
-
- context "unsuccessful password change" do
- it "should have current password incorrect" do
- do_patch('Letmeout1!', 'Letmein1!', 'Letmein1!')
- expect(@user.valid_password?('Letmeout1!')).to be_falsey
- expect(@user.valid_password?('Letmein1!')).to be_truthy
- expect(assigns[:current_user].errors[:current_password]).not_to be_blank
- end
-
- it "should not update password_changed_at" do
- do_patch('Letmeout1!', 'Letmein1!', 'Letmein1!')
- @user.reload
- expect(@user.password_changed_at).not_to be_within(1.second).of(Time.current)
- expect(@user.valid_password?('Letmein1!')).to be_truthy
- end
-
- it "should have current password and new password are the same" do
- do_patch('Letmein1!', 'Letmein1!', 'Letmein1!')
- expect(assigns[:current_user].errors[:password]).not_to be_blank
- @user.reload
- expect(@user.valid_password?('Letmein1!')).to be_truthy
- end
-
- it "should have new password as invalid" do
- do_patch('Letmein1!', 'aB1defgh', 'aB1defgh')
- expect(assigns[:current_user].errors[:password]).not_to be_blank
- @user.reload
- expect(@user.valid_password?('Letmein1!')).to be_truthy
- end
-
- it "should have password as invalid when confirmation is different" do
- do_patch('Letmein1!', 'aB1!efgh', 'aB1defgh')
- expect(assigns[:current_user].errors[:password_confirmation]).not_to be_blank
- @user.reload
- expect(@user.valid_password?('Letmein1!')).to be_truthy
- end
- end
- end
- end
-end
diff --git a/spec/controllers/admin/schedule_debate_controller_spec.rb b/spec/controllers/admin/schedule_debate_controller_spec.rb
index 2f191f2b9..9252e5ba2 100644
--- a/spec/controllers/admin/schedule_debate_controller_spec.rb
+++ b/spec/controllers/admin/schedule_debate_controller_spec.rb
@@ -41,25 +41,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/searches_controller_spec.rb b/spec/controllers/admin/searches_controller_spec.rb
index 6c1ba092f..a3ddde258 100644
--- a/spec/controllers/admin/searches_controller_spec.rb
+++ b/spec/controllers/admin/searches_controller_spec.rb
@@ -10,18 +10,6 @@
end
end
- context "when logged in as a moderator but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/search" do
- it "redirects to the edit profile page" do
- get :show, params: { type: "petition", q: "foo" }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as a moderator" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/signatures_controller_spec.rb b/spec/controllers/admin/signatures_controller_spec.rb
index 7f1342f75..1faa8d651 100644
--- a/spec/controllers/admin/signatures_controller_spec.rb
+++ b/spec/controllers/admin/signatures_controller_spec.rb
@@ -20,25 +20,6 @@
end
end
- context "logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe "GET /admin/signatures" do
- it "redirects to the login page" do
- get :index, params: { q: "Alice" }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe "DELETE /admin/signatures/:id" do
- it "redirects to edit profile page" do
- delete :destroy, params: { id: signature.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
let(:token) { SecureRandom.base64(32) }
diff --git a/spec/controllers/admin/take_down_controller_spec.rb b/spec/controllers/admin/take_down_controller_spec.rb
index c073a5a03..e323d89b4 100644
--- a/spec/controllers/admin/take_down_controller_spec.rb
+++ b/spec/controllers/admin/take_down_controller_spec.rb
@@ -48,25 +48,6 @@
end
end
- context 'logged in as moderator user but need to reset password' do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
- before { login_as(user) }
-
- describe 'GET /show' do
- it 'redirects to edit profile page' do
- get :show, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
-
- describe 'PATCH /update' do
- it 'redirects to edit profile page' do
- patch :update, params: { petition_id: petition.id }
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
before { login_as(user) }
diff --git a/spec/controllers/admin/trending_domains_controller_spec.rb b/spec/controllers/admin/trending_domains_controller_spec.rb
index 80a341ad7..16f01cdad 100644
--- a/spec/controllers/admin/trending_domains_controller_spec.rb
+++ b/spec/controllers/admin/trending_domains_controller_spec.rb
@@ -13,24 +13,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
-
- before do
- login_as(user)
- end
-
- describe "GET /admin/petitions/200000/trending-domains" do
- before do
- get :index, params: { petition_id: "200000" }
- end
-
- it "redirects to edit profile page" do
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
diff --git a/spec/controllers/admin/trending_ips_controller_spec.rb b/spec/controllers/admin/trending_ips_controller_spec.rb
index b9c3c0479..25c1712e8 100644
--- a/spec/controllers/admin/trending_ips_controller_spec.rb
+++ b/spec/controllers/admin/trending_ips_controller_spec.rb
@@ -13,24 +13,6 @@
end
end
- context "when logged in as moderator user but need to reset password" do
- let(:user) { FactoryBot.create(:moderator_user, force_password_reset: true) }
-
- before do
- login_as(user)
- end
-
- describe "GET /admin/petitions/200000/trending-ips" do
- before do
- get :index, params: { petition_id: "200000" }
- end
-
- it "redirects to edit profile page" do
- expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/profile/#{user.id}/edit")
- end
- end
- end
-
context "when logged in as moderator user" do
let(:user) { FactoryBot.create(:moderator_user) }
diff --git a/spec/factories.rb b/spec/factories.rb
index c4d03081a..7ba8c74db 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -4,11 +4,8 @@
FactoryBot.define do
factory :admin_user do
sequence(:email) { |n| "admin#{n}@example.com" }
- password { "Letmein1!" }
- password_confirmation { "Letmein1!" }
sequence(:first_name) { |n| "AdminUser#{n}" }
sequence(:last_name) { |n| "AdminUser#{n}" }
- force_password_reset { false }
end
factory :sysadmin_user, :parent => :admin_user do
@@ -80,6 +77,10 @@
opened_at { 2.years.ago }
closed_at { 1.year.ago }
+ trait :creator do
+ association :creator, :validated, :creator, factory: :archived_signature
+ end
+
after(:build) do |petition, evaluator|
petition.parliament ||= Parliament.archived.first || FactoryBot.create(:parliament, :archived)
@@ -219,7 +220,7 @@
location_code { "GB" }
ip_address { Faker::Internet.public_ip_v4_address }
state { Archived::Signature::VALIDATED_STATE }
- unsubscribe_token { Authlogic::Random.friendly_token }
+ unsubscribe_token { SecureRandom.base58(20) }
notify_by_email { true }
trait :creator do
@@ -258,6 +259,7 @@
creator_name { nil }
creator_email { nil }
creator_attributes { {} }
+ email_attributes { nil }
sponsors_signed { nil }
sponsor_count { Site.minimum_number_of_sponsors }
increment { true }
@@ -294,6 +296,10 @@
if evaluator.admin_notes
petition.build_note details: evaluator.admin_notes
end
+
+ if evaluator.email_attributes
+ petition.emails << build(:petition_email, petition: petition, **evaluator.email_attributes)
+ end
end
after(:create) do |petition, evaluator|
@@ -317,6 +323,11 @@
end
end
+ trait :archived do
+ archived_at { 3.day.ago }
+ archiving_started_at { 4.days.ago }
+ end
+
trait :with_additional_details do
additional_details { "Petition additional details" }
end
@@ -971,4 +982,82 @@
)
end
end
+
+ factory :sso_user, class: Hash do
+ transient do
+ email { "admin@example.com" }
+ first_name { "Sys" }
+ last_name { "Admin" }
+ groups { %w[sysadmins] }
+ end
+
+ provider { "example" }
+ uid { email }
+
+ info do
+ {
+ name: "#{first_name} #{last_name}",
+ email: email,
+ first_name: first_name,
+ last_name: last_name,
+ groups: groups
+ }
+ end
+
+ credentials do
+ {}
+ end
+
+ extra do
+ {
+ raw_info: {
+ name: "#{first_name} #{last_name}",
+ email: email,
+ first_name: first_name,
+ last_name: last_name,
+ groups: groups
+ }
+ }
+ end
+
+ skip_create
+
+ initialize_with { OmniAuth::AuthHash.new(attributes) }
+ end
+
+ factory :sysadmin_sso_user, parent: :sso_user do
+ transient do
+ email { "sysadmin@example.com" }
+ first_name { "John" }
+ last_name { "Admin" }
+ groups { %w[sysadmins] }
+ end
+ end
+
+ factory :moderator_sso_user, parent: :sso_user do
+ transient do
+ email { "moderator@example.com" }
+ first_name { "John" }
+ last_name { "Moderator" }
+ groups { %w[moderators] }
+ end
+ end
+
+ factory :reviewer_sso_user, parent: :sso_user do
+ transient do
+ email { "reviewer@example.com" }
+ first_name { "John" }
+ last_name { "Reviewer" }
+ groups { %w[reviewers] }
+ end
+ end
+
+ factory :norole_sso_user, parent: :sso_user do
+ transient do
+ email { "norole@example.com" }
+ first_name { "No" }
+ last_name { "Role" }
+ groups { %w[] }
+ end
+ end
end
diff --git a/spec/jobs/archive_petition_job_spec.rb b/spec/jobs/archive_petition_job_spec.rb
index 2ca51344c..cc9403835 100644
--- a/spec/jobs/archive_petition_job_spec.rb
+++ b/spec/jobs/archive_petition_job_spec.rb
@@ -287,7 +287,7 @@
end
end
- context "with a petition that has an government_response email scheduled" do
+ context "with a petition that has a government_response email scheduled" do
let(:petition) do
FactoryBot.create(:closed_petition, :email_requested, email_requested_for_government_response_at: Time.current)
end
@@ -297,7 +297,7 @@
end
end
- context "with a petition that has an debate_scheduled email scheduled" do
+ context "with a petition that has a debate_scheduled email scheduled" do
let(:petition) do
FactoryBot.create(:closed_petition, :email_requested, email_requested_for_debate_scheduled_at: Time.current)
end
@@ -307,7 +307,7 @@
end
end
- context "with a petition that has an debate_outcome email scheduled" do
+ context "with a petition that has a debate_outcome email scheduled" do
let(:petition) do
FactoryBot.create(:closed_petition, :email_requested, email_requested_for_debate_outcome_at: Time.current)
end
@@ -317,7 +317,7 @@
end
end
- context "with a petition that has an petition_email email scheduled" do
+ context "with a petition that has a petition_email email scheduled" do
let(:petition) do
FactoryBot.create(:closed_petition, :email_requested, email_requested_for_petition_email_at: Time.current)
end
@@ -327,7 +327,24 @@
end
end
- context "with a petition that has an petition_mailshot email scheduled" do
+ context "with a petition that has a petition_email email that has been sent" do
+ let(:petition) do
+ FactoryBot.create(:closed_petition, email_attributes: { email_count: 216, emails_enqueued_at: 6.months.ago })
+ end
+
+ let(:email) { petition.emails.first }
+ let(:archived_email) { archived_petition.emails.first }
+
+ it "copies the email count to the archived petition email" do
+ expect(archived_email.email_count).to eq(email.email_count)
+ end
+
+ it "copies the emails enqueued timestamp to the archived petition email" do
+ expect(archived_email.emails_enqueued_at).to be_usec_precise_with(email.emails_enqueued_at)
+ end
+ end
+
+ context "with a petition that has a petition_mailshot email scheduled" do
let(:petition) do
FactoryBot.create(:closed_petition, :email_requested, email_requested_for_petition_mailshot_at: Time.current)
end
diff --git a/spec/jobs/archived/email_petitioners_job_spec.rb b/spec/jobs/archived/email_petitioners_job_spec.rb
index 990a5d974..8cd4e569f 100644
--- a/spec/jobs/archived/email_petitioners_job_spec.rb
+++ b/spec/jobs/archived/email_petitioners_job_spec.rb
@@ -3,7 +3,7 @@
RSpec.describe Archived::EmailPetitionersJob, type: :job do
let(:email_requested_at) { Time.current }
- let(:petition) { FactoryBot.create(:archived_petition) }
+ let(:petition) { FactoryBot.create(:archived_petition, :creator) }
let(:signature) { FactoryBot.create(:archived_signature, petition: petition) }
let(:email) { FactoryBot.create(:archived_petition_email, petition: petition) }
let(:arguments) { { petition: petition, email: email } }
@@ -13,8 +13,32 @@
allow(petition).to receive_message_chain(:signatures_to_email_for, :find_each).and_yield(signature)
end
- it_behaves_like "job to enqueue signatory mailing jobs"
+ it_behaves_like "job to enqueue signatory mailing jobs", -> {
+ subject.perform(petition: petition, email: email, requested_at: requested_at_as_string)
+ }
+ context "when sending other parliamentary business emails" do
+ it "records the number of emails sent" do
+ expect {
+ perform_enqueued_jobs {
+ described_class.run_later_tonight(**arguments)
+ }
+ }.to change {
+ email.reload.email_count
+ }.from(nil).to(2)
+ end
+
+ it "records when the emails were enqueued by" do
+ expect {
+ perform_enqueued_jobs {
+ described_class.run_later_tonight(**arguments)
+ }
+ }.to change {
+ email.reload.emails_enqueued_at
+ }.from(nil).to(be_within(1.second).of(Time.current))
+ end
+ end
+
context "when the petition email has been deleted" do
before do
email.destroy
diff --git a/spec/jobs/delete_petition_job_spec.rb b/spec/jobs/delete_petition_job_spec.rb
index 5cf363d5c..a15cd8846 100644
--- a/spec/jobs/delete_petition_job_spec.rb
+++ b/spec/jobs/delete_petition_job_spec.rb
@@ -7,7 +7,7 @@
end
context "with a stopped petition" do
- let!(:petition) { FactoryBot.create(:stopped_petition) }
+ let!(:petition) { FactoryBot.create(:stopped_petition, :archived) }
it "destroys the petition" do
expect {
@@ -19,7 +19,7 @@
end
context "with a closed petition" do
- let!(:petition) { FactoryBot.create(:validated_petition, sponsors_signed: true, state: "closed", closed_at: 4.weeks.ago) }
+ let!(:petition) { FactoryBot.create(:closed_petition, :archived, sponsors_signed: true, closed_at: 4.weeks.ago) }
let!(:country_petition_journal) { FactoryBot.create(:country_petition_journal, petition: petition) }
let!(:constituency_petition_journal) { FactoryBot.create(:constituency_petition_journal, petition: petition) }
@@ -136,7 +136,7 @@
end
context "with a rejected petition" do
- let!(:petition) { FactoryBot.create(:rejected_petition) }
+ let!(:petition) { FactoryBot.create(:rejected_petition, :archived) }
it "destroys the petition" do
expect {
@@ -157,7 +157,7 @@
context "with a hidden petition" do
let!(:user) { FactoryBot.create(:moderator_user) }
- let!(:petition) { FactoryBot.create(:rejected_petition, rejection_code: "libellous", moderated_by: user) }
+ let!(:petition) { FactoryBot.create(:rejected_petition, :archived, rejection_code: "libellous", moderated_by: user) }
it "destroys the petition" do
expect {
diff --git a/spec/jobs/email_petitioners_job_spec.rb b/spec/jobs/email_petitioners_job_spec.rb
index 1180b5285..b76ac18a4 100644
--- a/spec/jobs/email_petitioners_job_spec.rb
+++ b/spec/jobs/email_petitioners_job_spec.rb
@@ -13,7 +13,31 @@
allow(petition).to receive_message_chain(:signatures_to_email_for, :find_each).and_yield(signature)
end
- it_behaves_like "job to enqueue signatory mailing jobs"
+ it_behaves_like "job to enqueue signatory mailing jobs", -> {
+ subject.perform(petition: petition, email: email, requested_at: requested_at_as_string)
+ }
+
+ context "when sending other parliamentary business emails" do
+ it "records the number of emails sent" do
+ expect {
+ perform_enqueued_jobs {
+ described_class.run_later_tonight(**arguments)
+ }
+ }.to change {
+ email.reload.email_count
+ }.from(nil).to(2)
+ end
+
+ it "records when the emails were enqueued by" do
+ expect {
+ perform_enqueued_jobs {
+ described_class.run_later_tonight(**arguments)
+ }
+ }.to change {
+ email.reload.emails_enqueued_at
+ }.from(nil).to(be_within(1.second).of(Time.current))
+ end
+ end
context "when the petition email has been deleted" do
before do
diff --git a/spec/jobs/shared_examples.rb b/spec/jobs/shared_examples.rb
index 8be28c967..196669a42 100644
--- a/spec/jobs/shared_examples.rb
+++ b/spec/jobs/shared_examples.rb
@@ -1,9 +1,11 @@
-RSpec.shared_examples_for "job to enqueue signatory mailing jobs" do
+RSpec.shared_examples_for "job to enqueue signatory mailing jobs" do |work_proc|
let(:requested_at) { Time.current.change(usec: 0) }
let(:requested_at_as_string) { requested_at.getutc.iso8601(6) }
- def do_work
- subject.perform(petition: petition, requested_at: requested_at_as_string)
+ unless work_proc.present?
+ work_proc = -> {
+ subject.perform(petition: petition, requested_at: requested_at_as_string)
+ }
end
describe '.run_later_tonight' do
@@ -47,18 +49,18 @@ def do_work
context "when the petition has not been updated" do
it "enqueues a job to send an email to each signatory" do
- do_work
+ instance_exec(&work_proc)
expect(enqueued_jobs.size).to eq(1)
end
it "the job is the correct type for the type of notification" do
- do_work
+ instance_exec(&work_proc)
job = enqueued_jobs.first
expect(job[:job]).to eq(subject.email_delivery_job_class)
end
it "the job has the expected arguments" do
- do_work
+ instance_exec(&work_proc)
args = enqueued_jobs.first[:args].first
expect(args["timestamp_name"]).to eq(subject.timestamp_name)
@@ -72,7 +74,7 @@ def do_work
end
it "does not enqueue any jobs to send emails" do
- do_work
+ instance_exec(&work_proc)
expect(enqueued_jobs).to be_empty
end
end
diff --git a/spec/models/admin_user_session_spec.rb b/spec/models/admin_user_session_spec.rb
deleted file mode 100644
index 65117cf53..000000000
--- a/spec/models/admin_user_session_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe AdminUserSession do
- describe "#last_login_attempt?" do
- before do
- Authlogic::Session::Base.controller = Authlogic::TestCase::MockController.new
- FactoryBot.create(:moderator_user, email: email)
- end
-
- let(:email) { "admin@petition.parliament.uk" }
- let(:params) { { email: email, password: "password" } }
- let(:user_session) { described_class.new(params) }
- let(:user) { AdminUser.find_by!(email: email) }
-
- context "when there are no failed login attempts" do
- before do
- user.update_columns(failed_login_count: 0)
- user_session.save
- end
-
- it "returns false" do
- expect(user_session.attempted_record).to be_present
- expect(user_session.last_login_attempt?).to be false
- end
- end
-
- context "when there are 3 failed login attempts" do
- before do
- user.update_columns(failed_login_count: 3)
- user_session.save
- end
-
- it "returns true" do
- expect(user_session.attempted_record).to be_present
- expect(user_session.last_login_attempt?).to be true
- end
- end
-
- context "when there are 4 failed login attempts" do
- before do
- user.update_columns(failed_login_count: 4)
- user_session.save
- end
-
- it "returns false" do
- expect(user_session.attempted_record).to be_present
- expect(user_session.last_login_attempt?).to be false
- end
- end
- end
-end
diff --git a/spec/models/admin_user_spec.rb b/spec/models/admin_user_spec.rb
index d7790b125..62c4a9ff4 100644
--- a/spec/models/admin_user_spec.rb
+++ b/spec/models/admin_user_spec.rb
@@ -4,20 +4,21 @@
describe "schema" do
it { is_expected.to have_db_column(:email).of_type(:string).with_options(null: false) }
it { is_expected.to have_db_column(:persistence_token).of_type(:string) }
- it { is_expected.to have_db_column(:crypted_password).of_type(:string) }
- it { is_expected.to have_db_column(:password_salt).of_type(:string) }
- it { is_expected.to have_db_column(:login_count).of_type(:integer).with_options(default: 0) }
- it { is_expected.to have_db_column(:failed_login_count).of_type(:integer).with_options(default: 0) }
- it { is_expected.to have_db_column(:current_login_at).of_type(:datetime) }
- it { is_expected.to have_db_column(:last_login_at).of_type(:datetime) }
- it { is_expected.to have_db_column(:current_login_ip).of_type(:string) }
- it { is_expected.to have_db_column(:last_login_ip).of_type(:string) }
+ it { is_expected.to have_db_column(:sign_in_count).of_type(:integer).with_options(default: 0) }
+ it { is_expected.to have_db_column(:current_sign_in_at).of_type(:datetime) }
+ it { is_expected.to have_db_column(:last_sign_in_at).of_type(:datetime) }
+ it { is_expected.to have_db_column(:current_sign_in_ip).of_type(:string) }
+ it { is_expected.to have_db_column(:last_sign_in_ip).of_type(:string) }
it { is_expected.to have_db_column(:role).of_type(:string).with_options(limit: 10, null: false) }
- it { is_expected.to have_db_column(:force_password_reset).of_type(:boolean).with_options(default: true) }
- it { is_expected.to have_db_column(:password_changed_at).of_type(:datetime) }
it { is_expected.to have_db_column(:created_at).of_type(:datetime) }
it { is_expected.to have_db_column(:updated_at).of_type(:datetime) }
- it { is_expected.to have_db_column(:last_request_at).of_type(:datetime) }
+
+ it { is_expected.not_to have_db_column(:encrypted_password).of_type(:string) }
+ it { is_expected.not_to have_db_column(:password_salt).of_type(:string) }
+ it { is_expected.not_to have_db_column(:failed_attempts).of_type(:integer).with_options(default: 0) }
+ it { is_expected.not_to have_db_column(:force_password_reset).of_type(:boolean).with_options(default: true) }
+ it { is_expected.not_to have_db_column(:password_changed_at).of_type(:datetime) }
+ it { is_expected.not_to have_db_column(:locked_at).of_type(:datetime) }
end
describe "indexes" do
@@ -25,23 +26,10 @@
it { is_expected.to have_db_index([:last_name, :first_name]) }
end
- describe "behaviours" do
- it { expect(AdminUser.respond_to?(:acts_as_authentic)).to be_truthy }
- end
-
- describe "defaults" do
- it "force_password_reset should default to true" do
- u = AdminUser.new
- expect(u.force_password_reset).to be_truthy
- end
- end
-
describe "validations" do
- it { is_expected.to validate_presence_of(:password) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_presence_of(:first_name) }
it { is_expected.to validate_presence_of(:last_name) }
- it { is_expected.to validate_length_of(:password).is_at_least(8) }
it { is_expected.to allow_value("oliver@opsb.co.uk").for(:email)}
it { is_expected.not_to allow_value("jimbo").for(:email) }
@@ -50,20 +38,6 @@
is_expected.to validate_uniqueness_of(:email).case_insensitive
end
- it "should only allow passwords with a digit, lower and upper case alpha and a special char" do
- ['Letmein1!', 'Letmein1_', '1Ab*aaaa'].each do |email|
- u = FactoryBot.build(:moderator_user, :password => email, :password_confirmation => email)
- expect(u).to be_valid
- end
- end
-
- it "should not allow passwords without a digit, lower and upper case alpha and a special char" do
- ['Letmein1', 'hell$0123', '^%ttttFFFFF', 'KJDL_3444'].each do |email|
- u = FactoryBot.build(:moderator_user, :password => email, :password_confirmation => email)
- expect(u).not_to be_valid
- end
- end
-
it "should allow sysadmin role" do
u = FactoryBot.build(:admin_user, :role => 'sysadmin')
expect(u).to be_valid
@@ -100,177 +74,6 @@
end
describe "instance methods" do
- describe "#update_with_password" do
- let!(:user) do
- FactoryBot.create(
- :sysadmin_user,
- password: "Testing!23",
- password_confirmation: "Testing!23",
- password_changed_at: nil,
- force_password_reset: true
- )
- end
-
- let(:params) do
- {
- current_password: current_password, password: password,
- password_confirmation: password_confirmation
- }
- end
-
- let(:current_password) { "Testing!23" }
- let(:password) { "NewP4ssword!" }
- let(:password_confirmation) { "NewP4ssword!" }
-
- context "when the new password is valid" do
- it "returns true" do
- expect(user.update_with_password(params)).to be_truthy
- end
-
- it "changes the crypted_password field" do
- expect {
- user.update_with_password(params)
- }.to change {
- user.reload.crypted_password
- }
- end
-
- it "sets the timestamp for when the password was changed" do
- expect {
- user.update_with_password(params)
- }.to change {
- user.reload.password_changed_at
- }.from(nil).to(be_within(1.second).of(Time.current))
- end
-
- it "clears the force password reset flag" do
- expect {
- user.update_with_password(params)
- }.to change {
- user.reload.force_password_reset
- }.from(true).to(false)
- end
- end
-
- context "when the current password is missing" do
- let(:current_password) { "" }
-
- it "returns false" do
- expect(user.update_with_password(params)).to be_falsey
- end
-
- it "adds an error" do
- user.update_with_password(params)
- expect(user.errors[:current_password]).to eq(["Current password can't be blank"])
- end
-
- it "doesn't clear the force password reset flag" do
- user.update_with_password(params)
- expect(user.force_password_reset).to be true
- end
-
- it "doesn't set the password_changed_at timestamp" do
- user.update_with_password(params)
- expect(user.password_changed_at).to be_nil
- end
- end
-
- context "when the current password is incorrect" do
- let(:current_password) { "L3tme!n" }
-
- it "returns false" do
- expect(user.update_with_password(params)).to be_falsey
- end
-
- it "adds an error" do
- user.update_with_password(params)
- expect(user.errors[:current_password]).to eq(["Current password is incorrect"])
- end
-
- it "doesn't clear the force password reset flag" do
- user.update_with_password(params)
- expect(user.force_password_reset).to be true
- end
-
- it "doesn't set the password_changed_at timestamp" do
- user.update_with_password(params)
- expect(user.password_changed_at).to be_nil
- end
- end
-
- context "when the new password is the same as the old password" do
- let(:password) { current_password }
- let(:password_confirmation) { current_password }
-
- it "returns false" do
- expect(user.update_with_password(params)).to be_falsey
- end
-
- it "adds an error" do
- user.update_with_password(params)
- expect(user.errors[:password]).to eq(["Password is the same as the current password"])
- end
-
- it "doesn't clear the force password reset flag" do
- user.update_with_password(params)
- expect(user.force_password_reset).to be true
- end
-
- it "doesn't set the password_changed_at timestamp" do
- user.update_with_password(params)
- expect(user.password_changed_at).to be_nil
- end
- end
-
- context "when the new password is invalid" do
- let(:password) { "password" }
- let(:password_confirmation) { "password" }
-
- it "returns false" do
- expect(user.update_with_password(params)).to be_falsey
- end
-
- it "adds an error" do
- user.update_with_password(params)
- expect(user.errors[:password]).to eq(["Password must contain at least one digit, a lower and upper case letter and a special character"])
- end
-
- it "doesn't clear the force password reset flag" do
- user.update_with_password(params)
- expect(user.force_password_reset).to be true
- end
-
- it "doesn't set the password_changed_at timestamp" do
- user.update_with_password(params)
- expect(user.password_changed_at).to be_nil
- end
- end
-
- context "when the new password doesn't match the confirmation" do
- let(:password) { "L3tme!n1" }
- let(:password_confirmation) { "L3tme!n2" }
-
- it "returns false" do
- expect(user.update_with_password(params)).to be_falsey
- end
-
- it "adds an error" do
- user.update_with_password(params)
- expect(user.errors[:password_confirmation]).to eq(["Password confirmation doesn't match password"])
- end
-
- it "doesn't clear the force password reset flag" do
- user.update_with_password(params)
- expect(user.force_password_reset).to be true
- end
-
- it "doesn't set the password_changed_at timestamp" do
- user.update_with_password(params)
- expect(user.password_changed_at).to be_nil
- end
- end
- end
-
describe "#destroy" do
context "when there is no current user and there is more than one" do
let!(:user_1) { FactoryBot.create(:sysadmin_user) }
@@ -342,28 +145,6 @@
end
end
- describe "#has_to_change_password?" do
- it "should be true when force_reset_password is true" do
- user = FactoryBot.create(:moderator_user, :force_password_reset => true)
- expect(user.has_to_change_password?).to be_truthy
- end
-
- it "should be false when force_reset_password is false" do
- user = FactoryBot.create(:moderator_user, :force_password_reset => false)
- expect(user.has_to_change_password?).to be_falsey
- end
-
- it "should be true when password was last changed over 9 months ago" do
- user = FactoryBot.create(:moderator_user, :force_password_reset => false, :password_changed_at => 9.months.ago - 1.minute)
- expect(user.has_to_change_password?).to be_truthy
- end
-
- it "should be false when password was last changed less than 9 months ago" do
- user = FactoryBot.create(:moderator_user, :force_password_reset => false, :password_changed_at => 9.months.ago + 1.minute)
- expect(user.has_to_change_password?).to be_falsey
- end
- end
-
describe "#can_take_petitions_down?" do
it "is true if the user is a sysadmin" do
user = FactoryBot.create(:admin_user, :role => 'sysadmin')
@@ -376,52 +157,21 @@
end
end
- describe "#account_disabled" do
- it "should return true when user has tried to login 5 times unsuccessfully" do
- user = FactoryBot.create(:moderator_user)
- user.failed_login_count = 5
- expect(user.account_disabled).to be_truthy
- end
-
- it "should return true when user has tried to login 6 times unsuccessfully" do
- user = FactoryBot.create(:moderator_user)
- user.failed_login_count = 6
- expect(user.account_disabled).to be_truthy
- end
-
- it "should return false when user has tried to login 4 times unsuccessfully" do
- user = FactoryBot.create(:moderator_user)
- user.failed_login_count = 4
- expect(user.account_disabled).to be_falsey
- end
- end
-
- describe "#account_disabled=" do
- it "should set the failed login count to 5 when true" do
- u = FactoryBot.create(:moderator_user)
- u.account_disabled = true
- expect(u.failed_login_count).to eq(5)
- end
-
- it "should set the failed login count to 0 when false" do
- u = FactoryBot.create(:moderator_user)
- u.failed_login_count = 5
- u.account_disabled = false
- expect(u.failed_login_count).to eq(0)
- end
- end
-
describe "#elapsed_time" do
it "returns the number of seconds since the last request" do
- user = FactoryBot.build(:admin_user, last_request_at: 60.seconds.ago)
- expect(user.elapsed_time).to eq(60)
+ user = FactoryBot.build(:admin_user)
+ expect(user.elapsed_time(60.seconds.ago)).to eq(60)
end
end
describe "#time_remaining" do
- it "returns the number of seconds since the last request" do
- user = FactoryBot.build(:admin_user, last_request_at: 60.seconds.ago)
- expect(user.elapsed_time).to eq(60)
+ before do
+ allow(Site).to receive(:login_timeout).and_return(300)
+ end
+
+ it "returns the number of seconds remaining until the user is logged out" do
+ user = FactoryBot.build(:admin_user)
+ expect(user.time_remaining(60.seconds.ago)).to eq(240)
end
end
end
diff --git a/spec/models/signature_spec.rb b/spec/models/signature_spec.rb
index 6df799492..5974dd3d9 100644
--- a/spec/models/signature_spec.rb
+++ b/spec/models/signature_spec.rb
@@ -2086,7 +2086,7 @@
end
context "when another process has updated the column" do
- let(:token) { Authlogic::Random.friendly_token }
+ let(:token) { SecureRandom.base58(20) }
before do
signature.update_column(:signed_token, nil)
diff --git a/spec/requests/admin_user_persistence_token_spec.rb b/spec/requests/admin_user_persistence_token_spec.rb
index 9327ab238..5dac1d99e 100644
--- a/spec/requests/admin_user_persistence_token_spec.rb
+++ b/spec/requests/admin_user_persistence_token_spec.rb
@@ -5,18 +5,17 @@
{
first_name: "System",
last_name: "Administrator",
- email: "admin@petition.parliament.uk",
- password: "L3tme1n!",
- password_confirmation: "L3tme1n!"
+ email: "admin@example.com"
}
end
let(:login_params) do
- { email: "admin@petition.parliament.uk", password: "L3tme1n!" }
+ { email: "admin@example.com" }
end
before do
- FactoryBot.create(:sysadmin_user, user_attributes)
+ sso_user = FactoryBot.create(:sysadmin_sso_user, **user_attributes)
+ OmniAuth.config.mock_auth[:example] = sso_user
end
def new_browser
@@ -30,44 +29,74 @@ def new_browser
context "when a new session is created" do
it "logs out existing sessions" do
s1 = new_browser
- s1.post "/admin/user_sessions", params: { admin_user_session: login_params }
+ s1.post "/admin/login", params: { user: login_params }
+
+ expect(s1.response.status).to eq(307)
+ expect(s1.response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example")
+
+ s1.follow_redirect!(params: s1.request.POST)
expect(s1.response.status).to eq(302)
- expect(s1.response.headers["Location"]).to eq("https://moderate.petition.parliament.uk/admin")
+ expect(s1.response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example/callback")
+
+ s1.follow_redirect!
+
+ expect(s1.response.status).to eq(200)
+ expect(s1.response).to have_header("Refresh", "0; url=https://moderate.petition.parliament.uk/admin")
s2 = new_browser
- s2.post "/admin/user_sessions", params: { admin_user_session: login_params }
+ s2.post "/admin/login", params: { user: login_params }
+
+ expect(s2.response.status).to eq(307)
+ expect(s2.response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example")
+
+ s2.follow_redirect!(params: s2.request.POST)
expect(s2.response.status).to eq(302)
- expect(s2.response.headers["Location"]).to eq("https://moderate.petition.parliament.uk/admin")
+ expect(s2.response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example/callback")
+
+ s2.follow_redirect!
+
+ expect(s2.response.status).to eq(200)
+ expect(s2.response).to have_header("Refresh", "0; url=https://moderate.petition.parliament.uk/admin")
s1.get("/admin")
expect(s1.response.status).to eq(302)
- expect(s1.response.headers["Location"]).to eq("https://moderate.petition.parliament.uk/admin/login")
+ expect(s1.response.location).to eq("https://moderate.petition.parliament.uk/admin/login")
end
end
context "when a session is destroyed" do
it "resets the persistence token" do
s1 = new_browser
- s1.post "/admin/user_sessions", params: { admin_user_session: login_params }
+ s1.post "/admin/login", params: { user: login_params }
+
+ expect(s1.response.status).to eq(307)
+ expect(s1.response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example")
+
+ s1.follow_redirect!(params: s1.request.POST)
expect(s1.response.status).to eq(302)
- expect(s1.response.headers["Location"]).to eq("https://moderate.petition.parliament.uk/admin")
+ expect(s1.response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example/callback")
+
+ s1.follow_redirect!
+
+ expect(s1.response.status).to eq(200)
+ expect(s1.response).to have_header("Refresh", "0; url=https://moderate.petition.parliament.uk/admin")
s2 = new_browser
- s2.cookies["admin_user_credentials"] = s1.cookies["admin_user_credentials"]
+ s2.cookies["_epets_session"] = s1.cookies["_epets_session"]
s1.get("/admin/logout")
expect(s1.response.status).to eq(302)
- expect(s1.response.headers["Location"]).to eq("https://moderate.petition.parliament.uk/admin/login")
+ expect(s1.response.location).to eq("https://moderate.petition.parliament.uk/admin/login")
s2.get("/admin")
expect(s2.response.status).to eq(302)
- expect(s2.response.headers["Location"]).to eq("https://moderate.petition.parliament.uk/admin/login")
+ expect(s2.response.location).to eq("https://moderate.petition.parliament.uk/admin/login")
end
end
@@ -81,8 +110,20 @@ def new_browser
Site.instance.update(login_timeout: 600)
travel_to 5.minutes.ago do
- post "/admin/user_sessions", params: { admin_user_session: login_params }
- expect(response).to redirect_to("/admin")
+ post "/admin/login", params: { user: login_params }
+
+ expect(response.status).to eq(307)
+ expect(response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example")
+
+ follow_redirect!(params: request.POST)
+
+ expect(response.status).to eq(302)
+ expect(response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example/callback")
+
+ follow_redirect!
+
+ expect(response.status).to eq(200)
+ expect(response).to have_header("Refresh", "0; url=https://moderate.petition.parliament.uk/admin")
end
get "/admin"
diff --git a/spec/requests/bad_requests_spec.rb b/spec/requests/bad_requests_spec.rb
index 67ec515d7..df982a87e 100644
--- a/spec/requests/bad_requests_spec.rb
+++ b/spec/requests/bad_requests_spec.rb
@@ -26,9 +26,7 @@
{
first_name: "System",
last_name: "Administrator",
- email: "admin@petition.parliament.uk",
- password: "L3tme1n!",
- password_confirmation: "L3tme1n!"
+ email: "admin@petition.parliament.uk"
}
end
diff --git a/spec/requests/csrf_token_spec.rb b/spec/requests/csrf_token_spec.rb
index 5ed37b7f6..4e94edbab 100644
--- a/spec/requests/csrf_token_spec.rb
+++ b/spec/requests/csrf_token_spec.rb
@@ -5,14 +5,17 @@
{
first_name: "System",
last_name: "Administrator",
- email: "admin@petition.parliament.uk",
- password: "L3tme1n!",
- password_confirmation: "L3tme1n!"
+ email: "admin@example.com"
}
end
let(:login_params) do
- { email: "admin@petition.parliament.uk", password: "L3tme1n!" }
+ { email: "admin@example.com" }
+ end
+
+ before do
+ sso_user = FactoryBot.create(:sysadmin_sso_user, **user_attributes)
+ OmniAuth.config.mock_auth[:example] = sso_user
end
let(:encrypted_csrf_token) do
@@ -33,10 +36,27 @@
get "/admin/login"
end
+ def do_login
+ post "/admin/login", params: { user: login_params, authenticity_token: authenticity_token }
+
+ expect(response.status).to eq(307)
+ expect(response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example")
+
+ follow_redirect!(params: request.POST)
+
+ expect(response.status).to eq(302)
+ expect(response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example/callback")
+
+ follow_redirect!
+
+ expect(response.status).to eq(200)
+ expect(response).to have_header("Refresh", "0; url=https://moderate.petition.parliament.uk/admin")
+ end
+
context "when a new session is created" do
it "resets the csrf token" do
expect {
- post "/admin/user_sessions", params: { admin_user_session: login_params, authenticity_token: authenticity_token }
+ do_login
}.to change {
session[:_csrf_token]
}
@@ -44,12 +64,16 @@
end
context "when a session is destroyed" do
+ before do
+ do_login
+ end
+
it "resets the csrf token" do
expect {
get "/admin/logout"
}.to change {
session[:_csrf_token]
- }
+ }.from(be_present).to(be_nil)
end
end
@@ -58,8 +82,7 @@
Site.instance.update(login_timeout: 600)
travel_to 5.minutes.ago do
- post "/admin/user_sessions", params: { admin_user_session: login_params, authenticity_token: authenticity_token }
- expect(response).to redirect_to("/admin")
+ do_login
end
get "/admin"
@@ -71,7 +94,7 @@
expect(response).to redirect_to("/admin/login")
}.to change {
session[:_csrf_token]
- }
+ }.from(be_present).to(be_nil)
end
Site.instance.update(login_timeout: 1800)
diff --git a/spec/requests/login_timeout_spec.rb b/spec/requests/login_timeout_spec.rb
index d39ae6c64..89ff55fec 100644
--- a/spec/requests/login_timeout_spec.rb
+++ b/spec/requests/login_timeout_spec.rb
@@ -5,17 +5,18 @@
{
first_name: "System",
last_name: "Administrator",
- email: "admin@petition.parliament.uk",
- password: "L3tme1n!",
- password_confirmation: "L3tme1n!"
+ email: "admin@example.com"
}
end
let(:login_params) do
- { email: "admin@petition.parliament.uk", password: "L3tme1n!" }
+ { email: "admin@example.com" }
end
- let!(:user) { FactoryBot.create(:sysadmin_user, user_attributes) }
+ before do
+ sso_user = FactoryBot.create(:sysadmin_sso_user, **user_attributes)
+ OmniAuth.config.mock_auth[:example] = sso_user
+ end
before do
host! "moderate.petition.parliament.uk"
@@ -26,8 +27,20 @@
Site.instance.update(login_timeout: 600)
travel_to 2.minutes.ago do
- post "/admin/user_sessions", params: { admin_user_session: login_params }
- expect(response).to redirect_to("/admin")
+ post "/admin/login", params: { user: login_params }
+
+ expect(response.status).to eq(307)
+ expect(response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example")
+
+ follow_redirect!(params: request.POST)
+
+ expect(response.status).to eq(302)
+ expect(response.location).to eq("https://moderate.petition.parliament.uk/admin/auth/example/callback")
+
+ follow_redirect!
+
+ expect(response.status).to eq(200)
+ expect(response).to have_header("Refresh", "0; url=https://moderate.petition.parliament.uk/admin")
end
get "/admin"
diff --git a/spec/routing/admin/petitions/government_response_spec.rb b/spec/routing/admin/petitions/government_response_spec.rb
index 372647a07..436cc20c0 100644
--- a/spec/routing/admin/petitions/government_response_spec.rb
+++ b/spec/routing/admin/petitions/government_response_spec.rb
@@ -21,7 +21,7 @@
expect(patch("/admin/petitions/1/government-response")).to route_to('admin/government_response#update', petition_id: '1')
end
- it "doesn't route DELETE /admin/petitions/1/government-response" do
- expect(delete("/admin/petitions/1/government-response")).not_to be_routable
+ it "routes DELETE /admin/petitions/1/government-response to admin/government_response#destroy" do
+ expect(delete("/admin/petitions/1/government-response")).to route_to('admin/government_response#destroy', petition_id: '1')
end
end
diff --git a/spec/routing/domain_constraints_spec.rb b/spec/routing/domain_constraints_spec.rb
index b3386554a..be1cce0fb 100644
--- a/spec/routing/domain_constraints_spec.rb
+++ b/spec/routing/domain_constraints_spec.rb
@@ -30,7 +30,7 @@
context "and making a request for an admin path" do
it "is routable" do
- expect(get("/admin/login")).to route_to("admin/user_sessions#new")
+ expect(get("/admin/login")).to route_to("admin/sessions#new")
end
end
diff --git a/spec/support/authentication.rb b/spec/support/authentication.rb
index 3f737e20f..83ee60779 100644
--- a/spec/support/authentication.rb
+++ b/spec/support/authentication.rb
@@ -1,10 +1,14 @@
-require 'authlogic/test_case'
-
RSpec.configure do |config|
mod = Module.new do
def login_as(user)
- activate_authlogic
- AdminUserSession.create(user)
+ sign_in(user, scope: :user)
+
+ # The devise sign_in test helper bypasses the hooks that set
+ # the token in the session so we have to set them manually.
+ session["warden.user.user.session"] = {
+ "persistence_token" => user.persistence_token,
+ "last_request_at" => Time.current.to_i
+ }
end
def http_authentication(username, password)
@@ -12,6 +16,6 @@ def http_authentication(username, password)
end
end
+ config.include(Devise::Test::ControllerHelpers, type: :controller)
config.include(mod, type: :controller)
- config.include(Authlogic::TestCase, type: :controller)
end
diff --git a/spec/support/omniauth.rb b/spec/support/omniauth.rb
new file mode 100644
index 000000000..841ce48f0
--- /dev/null
+++ b/spec/support/omniauth.rb
@@ -0,0 +1,16 @@
+RSpec.configure do |config|
+ config.around(:each, type: :request) do |example|
+ OmniAuth.config.test_mode = true
+ existing_failure_proc = OmniAuth.config.on_failure
+
+ OmniAuth.config.on_failure = Proc.new { |env|
+ OmniAuth::FailureEndpoint.new(env).redirect_to_failure
+ }
+
+ example.run
+ ensure
+ OmniAuth.config.mock_auth[:example] = nil
+ OmniAuth.config.on_failure = existing_failure_proc
+ OmniAuth.config.test_mode = false
+ end
+end
- <%= form_for @user_session do |f| %>
-
-
- <%= f.label :email, "Email", class: "form-label" %>
- <%= f.text_field :email, :class => 'form-control', :type => 'email', :autofocus => 'autofocus' %>
-
-
- <%= f.label :password, "Password", class: "form-label" %>
- <%= f.password_field :password, :class => 'form-control' %>
-
- <%= f.submit 'Sign in', :class => 'button' %>
- <% end %>
- |