Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/admin/editions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def permitted_edition_attributes
:scheduled_publication,
:lock_version,
:access_limited,
:access_limited_named_users,
:alternative_format_provider_id,
:opening_at,
:closing_at,
Expand Down
17 changes: 13 additions & 4 deletions app/models/concerns/edition/limited_access.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module Edition::LimitedAccess
extend ActiveSupport::Concern

included do
enum :access_limited, { disabled: 0, organisations: 1, named_users: 2 }
has_many :edition_user_accesses, dependent: :destroy, inverse_of: :edition
after_initialize :set_access_limited
end

Expand All @@ -16,15 +18,22 @@ def access_limited_object
end

def access_limited?
self[:access_limited]
organisations? || named_users?
end

delegate :access_limited_by_default?, to: :class

def access_limited=(value)
@_access_limited_explicitly_set = true
super
end

def set_access_limited
if new_record? && access_limited.nil?
self.access_limited = access_limited_by_default?
end
return unless new_record?
return if @_access_limited_explicitly_set

self.access_limited = access_limited_by_default? ? :organisations : :disabled
@_access_limited_explicitly_set = false
end

def accessible_to?(user)
Expand Down
10 changes: 10 additions & 0 deletions app/models/edition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,16 @@ def error_labels
{}
end

def access_limited_named_users=(users)
return unless Flipflop.enabled?(:access_limited_named_users)

user_emails = users.split(",").map(&:strip).reject(&:empty?)

user_emails.each do |email|
edition_user_accesses.create!(email: email)
end
end

private

def date_for_government
Expand Down
16 changes: 16 additions & 0 deletions app/models/edition_user_access.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class EditionUserAccess < ApplicationRecord
belongs_to :edition, inverse_of: :edition_user_accesses

validates :email,
presence: true,
uniqueness: { scope: :edition_id, case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }

before_destroy :prevent_destroy_if_locked

private

def prevent_destroy_if_locked
throw(:abort) if locked?
end
end
2 changes: 1 addition & 1 deletion app/services/edition_publisher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def verb

def prepare_edition
flag_if_political_content!
edition.access_limited = false
edition.access_limited = :disabled
edition.major_change_published_at = Time.zone.now unless edition.minor_change?
edition.make_public_at(edition.major_change_published_at)
edition.increment_version_number
Expand Down
48 changes: 34 additions & 14 deletions app/views/admin/editions/_access_limiting_fields.html.erb
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
<div class="govuk-!-margin-bottom-6">
<%= render "govuk_publishing_components/components/fieldset", {
legend_text: "Limit access",
heading_level: 3,
heading_size: "m",
} do %>
<%= form.hidden_field :access_limited, value: "0" %>

<%= render "govuk_publishing_components/components/checkboxes", {
<%= render "govuk_publishing_components/components/radio", {
heading: "Limit access",
name: "edition[access_limited]",
id: "edition_access_limited",
error_items: errors_for(edition.errors, :access_limited),
items: [
{
label: "Limit access to publishers from organisations associated with this document before you publish",
value: 1,
checked: edition.access_limited,
value: :disabled,
text: "No – This document should be available to all publishers",
bold: true,
checked: edition.disabled?,
},
],
{
value: :organisations,
text: "Limit access to publishers from organisations associated with this document",
bold: true,
checked: edition.organisations?,
},
(
Flipflop.enabled?(:access_limited_named_users) ?
{
value: :named_users,
text: "Limit access to named publishers",
bold: true,
checked: edition.named_users?,
conditional: render("govuk_publishing_components/components/textarea", {
label: {
text: "Add publishers who will have access",
bold: true,
},
name: "edition[access_limited_named_users]",
textarea_id: "edition_access_limited_named_users",
error_message: nil,
value: nil,
hint: "Add the emails of the publishers who will have access to this document before publishing. After publishing the document will be available to all publishers in the organisation associated with this document",
}),
}
: nil
),
].compact,
} %>
<% end %>
</div>
3 changes: 3 additions & 0 deletions config/features.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@
feature :configurable_document_types,
description: "Enable 'in development' config-driven document types (alongside the 'live' ones)",
default: Rails.env.development?
feature :access_limited_named_users,
description: "Allow documents to be access-limited to specific named editors by email",
default: false
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class MigrateAccessLimitedToIntegerEnum < ActiveRecord::Migration[8.0]
def up
safety_assured do
change_column :editions, :access_limited, :integer, null: false, default: 0
end
end

def down
safety_assured do
change_column :editions, :access_limited, :boolean, null: false
end
end
end
12 changes: 12 additions & 0 deletions db/migrate/20260507120000_create_edition_user_accesses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class CreateEditionUserAccesses < ActiveRecord::Migration[8.0]
def change
create_table :edition_user_accesses do |t|
t.references :edition, type: :integer, null: false, foreign_key: true
t.string :email, null: false
t.boolean :locked, null: false, default: false
t.timestamps
end

add_index :edition_user_accesses, %i[edition_id email], unique: true
end
end
15 changes: 13 additions & 2 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.1].define(version: 2026_05_03_161913) do
ActiveRecord::Schema[8.1].define(version: 2026_05_07_120000) do
create_table "assets", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.string "asset_manager_id", null: false
t.bigint "assetable_id"
Expand Down Expand Up @@ -327,6 +327,16 @@
t.index ["locale"], name: "index_edition_translations_on_locale"
end

create_table "edition_user_accesses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "edition_id", null: false
t.string "email", null: false
t.boolean "locked", default: false, null: false
t.datetime "updated_at", null: false
t.index ["edition_id", "email"], name: "index_edition_user_accesses_on_edition_id_and_email", unique: true
t.index ["edition_id"], name: "index_edition_user_accesses_on_edition_id"
end

create_table "edition_world_locations", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.datetime "created_at", precision: nil
t.integer "edition_id"
Expand All @@ -347,7 +357,7 @@
end

create_table "editions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.boolean "access_limited", null: false
t.integer "access_limited", default: 0, null: false
t.string "additional_related_mainstream_content_title"
t.string "additional_related_mainstream_content_url"
t.boolean "all_nation_applicability", default: true
Expand Down Expand Up @@ -1228,6 +1238,7 @@

add_foreign_key "documents", "editions", column: "latest_edition_id", on_update: :cascade, on_delete: :nullify
add_foreign_key "documents", "editions", column: "live_edition_id", on_update: :cascade, on_delete: :nullify
add_foreign_key "edition_user_accesses", "editions"
add_foreign_key "editions", "governments", on_delete: :nullify
add_foreign_key "link_checker_api_report_links", "link_checker_api_reports"
add_foreign_key "link_checker_api_reports", "editions"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@statistics_publication = create(
:publication,
:draft,
access_limited: false,
access_limited: :disabled,
publication_type_id: PublicationType::OfficialStatistics.id,
title:,
)
Expand Down
2 changes: 1 addition & 1 deletion features/step_definitions/most_recent_editions_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
current = Edition.find_by(title:).document.latest_edition
new_draft = current.create_draft(random_editor)
new_draft.organisations << org
new_draft.access_limited = true
new_draft.access_limited = :organisations
new_draft.change_note = "Limited to #{org.name}"
new_draft.save!
end
Expand Down
2 changes: 1 addition & 1 deletion test/components/admin/editions/tags_component_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Admin::Editions::TagsComponentTest < ViewComponent::TestCase
end

test "adds an access limited tag if edition has limited access" do
edition = build(:edition, access_limited: true)
edition = build(:edition, access_limited: 1)

expected_output = "<span class=\"govuk-tag govuk-tag--s govuk-tag--blue\">Draft</span> " \
"<span class=\"govuk-tag govuk-tag--s govuk-tag--red\">Limited access</span>"
Expand Down
2 changes: 1 addition & 1 deletion test/factories/editions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
scheduled_publication { 7.days.from_now }
end

trait(:access_limited) { access_limited { true } }
trait(:access_limited) { access_limited { :organisations } }

trait(:with_alternative_format_provider) do
association :alternative_format_provider, factory: :organisation_with_alternative_format_contact_email
Expand Down
24 changes: 12 additions & 12 deletions test/functional/admin/edition_access_limited_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase

edition = create(
:consultation,
access_limited: true,
access_limited: :organisations,
create_default_organisation: false,
lead_organisations: [organisation],
)

get :edit, params: { id: edition }

assert_select "form[action='#{update_access_limited_admin_edition_path(edition.id)}']" do
assert_select "input[name='edition[access_limited]'][type=checkbox][checked=checked]"
assert_select "input[name='edition[access_limited]'][type=radio][checked=checked]"
assert_select "textarea[name='edition[editorial_remark]']"

(1..4).each do |i|
Expand Down Expand Up @@ -58,7 +58,7 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase

edition = create(
:consultation,
access_limited: true,
access_limited: :organisations,
create_default_organisation: false,
lead_organisations: [first_organisation],
supporting_organisations: [second_organisation],
Expand All @@ -75,7 +75,7 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase
lead_organisation_ids: [second_organisation.id],
supporting_organisation_ids: [third_organisation.id],
editorial_remark:,
access_limited: "1",
access_limited: :organisations,
},
}

Expand All @@ -92,7 +92,7 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase

edition = create(
:consultation,
access_limited: true,
access_limited: :organisations,
create_default_organisation: false,
lead_organisations: [first_organisation],
supporting_organisations: [second_organisation],
Expand All @@ -107,13 +107,13 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase
lead_organisation_ids: [first_organisation.id],
supporting_organisation_ids: [second_organisation.id],
editorial_remark:,
access_limited: "0",
access_limited: :disabled,
},
}

assert_equal [first_organisation], edition.reload.lead_organisations
assert_equal [second_organisation], edition.supporting_organisations
assert_not edition.access_limited
assert_not edition.reload.access_limited?
assert_redirected_to admin_editions_path
assert_equal "Access updated for #{edition.title}", flash[:notice]
assert_equal "Access options updated by GDS Admin: #{editorial_remark}", edition.editorial_remarks.last.body
Expand All @@ -125,7 +125,7 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase

edition = create(
:consultation,
access_limited: true,
access_limited: :organisations,
create_default_organisation: false,
lead_organisations: [first_organisation],
supporting_organisations: [second_organisation],
Expand All @@ -138,13 +138,13 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase
lead_organisation_ids: [first_organisation.id],
supporting_organisation_ids: [second_organisation.id],
editorial_remark: "",
access_limited: "0",
access_limited: :disabled,
},
}

assert_template :edit
assert_equal ["Editorial remark cannot be blank"], assigns(:edition).errors.full_messages
assert edition.reload.access_limited
assert edition.reload.access_limited?
end

test "PATCH :update doesn't create an editorial remark or re-render with an error when nothing has changed" do
Expand All @@ -153,7 +153,7 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase

edition = create(
:consultation,
access_limited: true,
access_limited: :organisations,
create_default_organisation: false,
lead_organisations: [first_organisation],
supporting_organisations: [second_organisation],
Expand All @@ -166,7 +166,7 @@ class Admin::EditionAccessLimitedControllerTest < ActionController::TestCase
lead_organisation_ids: [first_organisation.id],
supporting_organisation_ids: [second_organisation.id],
editorial_remark: "",
access_limited: "1",
access_limited: :organisations,
},
}

Expand Down
Loading
Loading