From 9318a2a653868dfbca1322222b1003ee22e82111 Mon Sep 17 00:00:00 2001 From: dbwinger Date: Thu, 13 Apr 2023 15:10:17 -0400 Subject: [PATCH] Add ability to restrict users to specific languages --- .../alchemy/admin/current_language.rb | 18 ++ app/decorators/alchemy/user_with_languages.rb | 36 ++++ app/helpers/alchemy/admin/base_helper.rb | 4 +- app/models/alchemy/language.rb | 1 + app/models/alchemy/page.rb | 14 +- app/models/alchemy/page/page_natures.rb | 4 +- .../partials/_language_tree_select.html.erb | 4 +- .../alchemy/admin/pictures/_infos.html.erb | 4 +- ...07153522_create_alchemy_users_languages.rb | 13 ++ lib/alchemy/permissions.rb | 59 ++++-- spec/dummy/app/models/dummy_user.rb | 10 + ..._create_alchemy_users_languages.alchemy.rb | 14 ++ spec/dummy/db/schema.rb | 9 - .../features/admin/navigation_feature_spec.rb | 1 + .../admin/site_select_feature_spec.rb | 2 + spec/libraries/permissions_spec.rb | 172 +++++++++++++++--- spec/views/admin/pictures/show_spec.rb | 1 + 17 files changed, 302 insertions(+), 64 deletions(-) create mode 100644 app/decorators/alchemy/user_with_languages.rb create mode 100644 db/migrate/20230407153522_create_alchemy_users_languages.rb create mode 100644 spec/dummy/db/migrate/20230419135736_create_alchemy_users_languages.alchemy.rb diff --git a/app/controllers/concerns/alchemy/admin/current_language.rb b/app/controllers/concerns/alchemy/admin/current_language.rb index d2bf50a6e7..2071d68ab1 100644 --- a/app/controllers/concerns/alchemy/admin/current_language.rb +++ b/app/controllers/concerns/alchemy/admin/current_language.rb @@ -6,11 +6,29 @@ module CurrentLanguage extend ActiveSupport::Concern included do + prepend_before_action :redirect_to_accessible_site_language, only: :index before_action :load_current_language end private + def current_alchemy_user_with_languages + UserWithLanguages.new(current_alchemy_user) + end + + # If the current alchemy user has not been given access to the current site/language, change them to ones the user has access to. + def redirect_to_accessible_site_language + if Alchemy::Language.current + if current_alchemy_user_with_languages.accessible_sites.exclude? Alchemy::Site.current + set_alchemy_language current_alchemy_user_with_languages.accessible_languages.first + @current_alchemy_site = Language.current.site + set_current_alchemy_site + elsif current_alchemy_user_with_languages.accessible_languages.exclude? Alchemy::Language.current + set_alchemy_language current_alchemy_user_with_languages.accessible_languages.on_current_site.first + end + end + end + def load_current_language @current_language = Alchemy::Language.current if @current_language.nil? diff --git a/app/decorators/alchemy/user_with_languages.rb b/app/decorators/alchemy/user_with_languages.rb new file mode 100644 index 0000000000..917d8ad2f8 --- /dev/null +++ b/app/decorators/alchemy/user_with_languages.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Alchemy + class UserWithLanguages < SimpleDelegator + alias_method :user, :__getobj__ + + def language_restriction_implemented? + user.respond_to? :languages + end + + def accessible_languages + language_restriction_implemented? ? super : Alchemy::Language.all + end + + def accessible_language_ids + accessible_languages.pluck(:id) + end + + def languages_restricted? + !language_restriction_implemented? || + accessible_languages != Alchemy::Language.all + end + + def accessible_site_ids + accessible_languages.map(&:site_id).uniq + end + + def accessible_sites + Alchemy::Site.where(id: accessible_site_ids).order(:id) + end + + def can_access_language?(language) + accessible_languages.where(id: language.id).any? + end + end +end diff --git a/app/helpers/alchemy/admin/base_helper.rb b/app/helpers/alchemy/admin/base_helper.rb index 6de4756616..99ce8dd8a3 100644 --- a/app/helpers/alchemy/admin/base_helper.rb +++ b/app/helpers/alchemy/admin/base_helper.rb @@ -70,9 +70,7 @@ def translations_for_select # Used for site selector in Alchemy cockpit. def sites_for_select - Alchemy::Site.all.map do |site| - [site.name, site.id] - end + UserWithLanguages.new(current_alchemy_user).accessible_sites.pluck(:name, :id) end # Returns a javascript driven live filter for lists. diff --git a/app/models/alchemy/language.rb b/app/models/alchemy/language.rb index 386deef720..28bcb191ae 100644 --- a/app/models/alchemy/language.rb +++ b/app/models/alchemy/language.rb @@ -27,6 +27,7 @@ class Language < BaseRecord belongs_to :site has_many :pages, inverse_of: :language has_many :nodes, inverse_of: :language + has_and_belongs_to_many :users, class_name: Alchemy.user_class_name, join_table: :alchemy_users_languages before_validation :set_locale, if: -> { locale.blank? } diff --git a/app/models/alchemy/page.rb b/app/models/alchemy/page.rb index b98c75f2ef..4f0e3c7152 100644 --- a/app/models/alchemy/page.rb +++ b/app/models/alchemy/page.rb @@ -471,15 +471,19 @@ def attribute_fixed?(name) fixed_attributes.fixed?(name) end - # Checks the current page's list of editors, if defined. + # Checks the current page's list of editors, if defined, and the user's accessible languages, if they are restricted # - # This allows us to pass in a user and see if any of their roles are enable - # them to make edits + # This allows us to pass in a user and see if any of their roles/languages enable them to make edits # def editable_by?(user) - return true unless has_limited_editors? + user = UserWithLanguages.new(user) - (editor_roles & user.alchemy_roles).any? + if has_limited_editors? || user.languages_restricted? + (!has_limited_editors? || (editor_roles & user.alchemy_roles).any?) && + user.accessible_languages.include?(language) + else + true + end end # Returns the value of +public_on+ attribute from public version diff --git a/app/models/alchemy/page/page_natures.rb b/app/models/alchemy/page/page_natures.rb index 6e1adbde67..3e8deda976 100644 --- a/app/models/alchemy/page/page_natures.rb +++ b/app/models/alchemy/page/page_natures.rb @@ -42,9 +42,7 @@ def has_limited_editors? end def editor_roles - return unless has_limited_editors? - - definition["editable_by"] + has_limited_editors? ? definition["editable_by"] : [] end # True if page locked_at timestamp and locked_by id are set diff --git a/app/views/alchemy/admin/partials/_language_tree_select.html.erb b/app/views/alchemy/admin/partials/_language_tree_select.html.erb index 5dd6793020..cb49b11b97 100644 --- a/app/views/alchemy/admin/partials/_language_tree_select.html.erb +++ b/app/views/alchemy/admin/partials/_language_tree_select.html.erb @@ -1,5 +1,5 @@ -<% languages = Alchemy::Language.on_current_site %> -<% if can?(:switch_language, Alchemy::Page) && languages.many? %> +<% languages = Alchemy::Language.accessible_by(Alchemy::Permissions.new(current_alchemy_user), :switch_language).on_current_site %> +<% if can?(:switch_language, Alchemy::Page) && Alchemy::Language.current.site.languages.many? %>
<%= form_tag switch_admin_languages_path, method: 'get' do %> <%= select_tag( diff --git a/app/views/alchemy/admin/pictures/_infos.html.erb b/app/views/alchemy/admin/pictures/_infos.html.erb index 9e25ce973a..4bc8494ffb 100644 --- a/app/views/alchemy/admin/pictures/_infos.html.erb +++ b/app/views/alchemy/admin/pictures/_infos.html.erb @@ -27,12 +27,12 @@
  • <%= render_icon 'file-alt' %> -

    <%= link_to page.name, edit_admin_page_path(page) %>

    +

    <%= link_to_if can?(:edit, page), page.name, edit_admin_page_path(page) %>

      <% picture_ingredients.group_by(&:element).each do |element, picture_ingredients| %>
    • - <% page_link = link_to element.display_name_with_preview_text, + <% page_link = link_to_if can?(:edit, page), element.display_name_with_preview_text, edit_admin_page_path(page, anchor: "element_#{element.id}") %> <% ingredients = picture_ingredients.map { |p| Alchemy::IngredientEditor.new(p).translated_role }.to_sentence %> <% if element.public? %> diff --git a/db/migrate/20230407153522_create_alchemy_users_languages.rb b/db/migrate/20230407153522_create_alchemy_users_languages.rb new file mode 100644 index 0000000000..5b7174d2f8 --- /dev/null +++ b/db/migrate/20230407153522_create_alchemy_users_languages.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class CreateAlchemyUsersLanguages < ActiveRecord::Migration[6.1] + def change + create_table :alchemy_users_languages do |t| + t.bigint :user_id, null: false + t.bigint :language_id, full: false + + t.index :user_id + t.index :language_id + end + end +end diff --git a/lib/alchemy/permissions.rb b/lib/alchemy/permissions.rb index 9eec7cd772..26977eaf72 100644 --- a/lib/alchemy/permissions.rb +++ b/lib/alchemy/permissions.rb @@ -41,8 +41,8 @@ def alchemy_guest_user_rules e.public? && !e.restricted? end - can :read, Alchemy::Page, Alchemy::Page.published.not_restricted.from_current_site do |p| - p.public? && !p.restricted? && p.site == Alchemy::Site.current + can :read, Alchemy::Page, Alchemy::Page.published.not_restricted do |p| + p.public? && !p.restricted? end end end @@ -64,8 +64,8 @@ def alchemy_member_rules e.public? end - can :read, Alchemy::Page, Alchemy::Page.published.from_current_site do |p| - p.public? && p.site == Alchemy::Site.current + can :read, Alchemy::Page, Alchemy::Page.published do |p| + p.public? end end end @@ -89,7 +89,7 @@ def alchemy_author_rules :alchemy_admin_pages, :alchemy_admin_pictures, :alchemy_admin_tags, - :alchemy_admin_users + :alchemy_admin_users, ] # Controller actions @@ -124,19 +124,46 @@ module EditorUser def alchemy_editor_rules alchemy_author_rules - # Navigation - can :index, [ - :alchemy_admin_languages, - :alchemy_admin_users - ] - # Resources + if @user.accessible_languages.any? + # Navigation + # Allow to view (but not edit) all languages in sites for which the user has access to at least one language + can :index, :alchemy_admin_languages + can :index, Alchemy::Language, site_id: @user.accessible_site_ids + + # Resources + can [ + :copy, + :copy_language_tree, + :flush, + :order, + :switch_language, + ], Alchemy::Page, Alchemy::Page.where(language: @user.accessible_languages) do |page| + @user.can_access_language? page.language + end + + # Resources which may be locked via template permissions + # + # # config/alchemy/page_layouts.yml + # - name: contact + # editable_by: + # - freelancer + # - admin + # + can :publish, Alchemy::Page, Alchemy::Page.where(language: { id: @user.accessible_languages, public: true }) do |page| + page.language.public? && page.editable_by?(@user.user) + end + can :switch, Alchemy::Language + elsif Alchemy::Language.none? + can :index, Alchemy::Language + end + can [ :copy, :copy_language_tree, :flush, :order, - :switch_language + :switch_language, ], Alchemy::Page # Resources which may be locked via template permissions @@ -149,10 +176,12 @@ def alchemy_editor_rules # can([ :create, - :destroy - ], Alchemy::Page) { |p| p.editable_by?(@user) } + :destroy, + ], Alchemy::Page, Alchemy::Page.where(language: @user.accessible_languages) do |page| + page.editable_by?(@user.user) + end - can(:publish, Alchemy::Page) do |page| + can(:publish, Alchemy::Page, Alchemy::Page.where(language: @user.accessible_languages)) do |page| page.language.public? && page.editable_by?(@user) end diff --git a/spec/dummy/app/models/dummy_user.rb b/spec/dummy/app/models/dummy_user.rb index 1f8f9f9811..794dca7429 100644 --- a/spec/dummy/app/models/dummy_user.rb +++ b/spec/dummy/app/models/dummy_user.rb @@ -2,6 +2,8 @@ class DummyUser < ActiveRecord::Base has_many :folded_pages, class_name: "Alchemy::FoldedPage" + has_and_belongs_to_many :languages, class_name: "Alchemy::Language", foreign_key: :user_id, join_table: :alchemy_users_languages + attr_writer :alchemy_roles, :name def self.logged_in @@ -23,4 +25,12 @@ def name def human_roles_string alchemy_roles.map(&:humanize) end + + # Languages this user is allowed to access + # + # An empty collection means allow all languages + # + def accessible_languages + languages.any? ? languages : Alchemy::Language.all + end end diff --git a/spec/dummy/db/migrate/20230419135736_create_alchemy_users_languages.alchemy.rb b/spec/dummy/db/migrate/20230419135736_create_alchemy_users_languages.alchemy.rb new file mode 100644 index 0000000000..34d5f993b8 --- /dev/null +++ b/spec/dummy/db/migrate/20230419135736_create_alchemy_users_languages.alchemy.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# This migration comes from alchemy (originally 20230407153522) +class CreateAlchemyUsersLanguages < ActiveRecord::Migration[6.1] + def change + create_table :alchemy_users_languages do |t| + t.bigint :user_id, null: false + t.bigint :language_id, full: false + + t.index :user_id + t.index :language_id + end + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index e2647e4fc0..da98d6fc30 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -134,12 +134,6 @@ t.index ["updater_id"], name: "index_alchemy_nodes_on_updater_id" end - create_table "alchemy_page_mutexes", force: :cascade do |t| - t.integer "page_id", null: false - t.datetime "created_at" - t.index ["page_id"], name: "index_alchemy_page_mutexes_on_page_id", unique: true - end - create_table "alchemy_page_versions", force: :cascade do |t| t.integer "page_id", null: false t.datetime "public_on", precision: nil @@ -208,8 +202,6 @@ t.integer "image_file_size" t.string "image_file_format" t.index ["creator_id"], name: "index_alchemy_pictures_on_creator_id" - t.index ["image_file_name"], name: "index_alchemy_pictures_on_image_file_name" - t.index ["name"], name: "index_alchemy_pictures_on_name" t.index ["updater_id"], name: "index_alchemy_pictures_on_updater_id" end @@ -289,7 +281,6 @@ add_foreign_key "alchemy_languages", "alchemy_sites", column: "site_id" add_foreign_key "alchemy_nodes", "alchemy_languages", column: "language_id" add_foreign_key "alchemy_nodes", "alchemy_pages", column: "page_id", on_delete: :restrict - add_foreign_key "alchemy_page_mutexes", "alchemy_pages", column: "page_id" add_foreign_key "alchemy_page_versions", "alchemy_pages", column: "page_id", on_delete: :cascade add_foreign_key "alchemy_pages", "alchemy_languages", column: "language_id" add_foreign_key "alchemy_picture_thumbs", "alchemy_pictures", column: "picture_id" diff --git a/spec/features/admin/navigation_feature_spec.rb b/spec/features/admin/navigation_feature_spec.rb index b847a04b57..e50e1c32dd 100644 --- a/spec/features/admin/navigation_feature_spec.rb +++ b/spec/features/admin/navigation_feature_spec.rb @@ -14,6 +14,7 @@ context "editor users" do let!(:default_site) { create(:alchemy_site) } + let!(:default_language) { create(:alchemy_language) } before { authorize_user(:as_editor) } it "can access the languages page" do diff --git a/spec/features/admin/site_select_feature_spec.rb b/spec/features/admin/site_select_feature_spec.rb index 50a22fde28..d7e585437c 100644 --- a/spec/features/admin/site_select_feature_spec.rb +++ b/spec/features/admin/site_select_feature_spec.rb @@ -16,7 +16,9 @@ context "with multiple sites" do let!(:default_site) { create(:alchemy_site, :default) } + let!(:default_language) { create(:alchemy_language) } let!(:a_site) { create(:alchemy_site) } + let!(:a_language) { create(:alchemy_language, site: a_site) } context "not on pages or languages module" do it "does not display the site select" do diff --git a/spec/libraries/permissions_spec.rb b/spec/libraries/permissions_spec.rb index 693fac00c2..58b927c76f 100644 --- a/spec/libraries/permissions_spec.rb +++ b/spec/libraries/permissions_spec.rb @@ -17,6 +17,7 @@ let(:published_element) { mock_model(Alchemy::Element, restricted?: false, public?: true) } let(:restricted_element) { mock_model(Alchemy::Element, restricted?: true, public?: true) } let(:language) { build(:alchemy_language) } + let(:user_with_languages) { Alchemy::UserWithLanguages.new(user) } context "A guest user" do let(:user) { nil } @@ -86,16 +87,25 @@ is_expected.to be_able_to(:info, :alchemy_admin_dashboard) end - it "can edit page content" do - is_expected.to be_able_to(:show, unpublic_page) - is_expected.to be_able_to(:index, Alchemy::Page) - is_expected.to be_able_to(:info, Alchemy::Page) - is_expected.to be_able_to(:configure, Alchemy::Page) - is_expected.to be_able_to(:update, Alchemy::Page) - is_expected.to be_able_to(:fold, Alchemy::Page) - is_expected.to be_able_to(:link, Alchemy::Page) - is_expected.to be_able_to(:visit, Alchemy::Page) - is_expected.to be_able_to(:unlock, Alchemy::Page) + context "on a page editable by them" do + before { allow(unpublic_page).to receive(:editable_by?).with(user) { true } } + + it "can edit page content" do + [:show, :index, :info, :configure, :update, :fold, :link, :visit, :unlock].each do |action| + is_expected.to be_able_to(action, unpublic_page) + is_expected.to be_able_to(action, Alchemy::Page) + end + end + end + + context "on a page not editable by them" do + before { allow(unpublic_page).to receive(:editable_by?).with(user) { false } } + + it "cannot edit page content" do + [:info, :configure, :update, :fold, :link, :visit, :unlock].each do |action| + is_expected.not_to be_able_to(action, unpublic_page) + end + end end it "can not publish pages" do @@ -132,28 +142,91 @@ it "can index layoutpages" do is_expected.to be_able_to(:index, :alchemy_admin_layoutpages) end + + it "can only manage nodes for languages they have access to" do + user = build(:alchemy_dummy_user, :as_author, languages: create_list(:alchemy_language, 1)) + is_expected.to be_able_to(:manage, create(:alchemy_node, language: user.languages.first)) + is_expected.not_to be_able_to(:manage, create(:alchemy_node, language: create(:alchemy_language, :german))) + end end context "An editor" do let(:user) { build(:alchemy_dummy_user, :as_editor) } - it "can manage pages" do - is_expected.to be_able_to(:copy, Alchemy::Page) - is_expected.to be_able_to(:copy_language_tree, Alchemy::Page) - is_expected.to be_able_to(:create, Alchemy::Page) - is_expected.to be_able_to(:destroy, Alchemy::Page) - is_expected.to be_able_to(:flush, Alchemy::Page) - is_expected.to be_able_to(:order, Alchemy::Page) - is_expected.to be_able_to(:switch_language, Alchemy::Page) - is_expected.to be_able_to(:switch, Alchemy::Language) + context "on a page in a language they can access" do + before { unpublic_page.save } + + let(:user) { create(:alchemy_dummy_user, :as_editor, languages: [unpublic_page.language]) } + + it "can copy/copy language tree/flush/order/switch_language it" do + [:copy, :copy_language_tree, :flush, :order, :switch_language].each do |action| + is_expected.to be_able_to(action, unpublic_page) + expect(Alchemy::Page.accessible_by(subject, action)).to include unpublic_page + end + end + end + + context "on a page in a language they can access" do + before { unpublic_page.save } + + let(:user) { create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) } + + it "cannot copy/copy language tree/flush/order/switch_language it" do + [:copy, :copy_language_tree, :flush, :order, :switch_language].each do |action| + is_expected.not_to be_able_to(action, unpublic_page) + expect(Alchemy::Page.accessible_by(subject, action)).not_to include unpublic_page + end + end + + context "when it is editable by them" do + before { allow(unpublic_page).to receive(:editable_by?).with(user) { true } } + + it "can create and destroy" do + is_expected.to be_able_to :create, unpublic_page + is_expected.to be_able_to :destroy, unpublic_page + end + end + + context "when it is not editable by them" do + before { allow(unpublic_page).to receive(:editable_by?).with(user) { false } } + + it "can create and destroy" do + is_expected.not_to be_able_to :create, unpublic_page + is_expected.not_to be_able_to :destroy, unpublic_page + end + end end context "if page language is public" do let(:language) { create(:alchemy_language, :german, public: true) } let(:page) { create(:alchemy_page, language: language) } - it "can publish pages" do - is_expected.to be_able_to(:publish, page) + context "and in a language they have access to" do + let(:user) { build :alchemy_dummy_user, :as_editor, languages: [language] } + + context "and it is editable by them" do + before { allow(page).to receive(:editable_by?).with(user) { true } } + + it "can publish pages" do + is_expected.to be_able_to(:publish, page) + end + end + + context "and it is not editable by them" do + before { allow(page).to receive(:editable_by?).with(user) { false } } + + it "cannot publish pages" do + is_expected.not_to be_able_to(:publish, page) + end + end + end + + context "and not in a language accessible to them" do + let(:user) { build :alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :klingon) } + + it "cannot publish pages" do + is_expected.not_to be_able_to(:publish, page) + end end end @@ -161,6 +234,8 @@ let(:language) { create(:alchemy_language, :german, public: false) } let(:page) { create(:alchemy_page, language: language) } + before { allow(page).to receive(:editable_by?).with(user) { true } } + it "cannot publish pages" do is_expected.to_not be_able_to(:publish, page) end @@ -177,6 +252,24 @@ it "can manage tags" do is_expected.to be_able_to(:manage, Alchemy::Tag) end + + it "can index languages in sites they can access" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.to be_able_to( + :index, + create(:alchemy_language, site_id: user_with_languages.accessible_site_ids.first) + ) + end + + it "cannot index languages in sites they cannot access" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.not_to be_able_to( + :index, + create(:alchemy_language, site_id: create(:alchemy_site, host: "abc.def").id) + ) + end end context "An admin" do @@ -186,12 +279,41 @@ is_expected.to be_able_to(:update_check, :alchemy_admin_dashboard) end - it "can manage languages" do - is_expected.to be_able_to(:manage, Alchemy::Language) + it "can index a site they can access" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.to be_able_to(:index, user_with_languages.accessible_sites.first) end - it "can manage sites" do - is_expected.to be_able_to(:manage, Alchemy::Site) + it "cannot index a site they cannot access" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.to be_able_to(:index, create(:alchemy_site, host: "abc.def")) + end + + it "can manage a language they have access to" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.to be_able_to(:manage, user.languages.first) + expect(Alchemy::Language.accessible_by(subject, :manage)).to match_array(user.languages) + end + + it "cannot manage a language they do not have access to" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.not_to be_able_to(:manage, create(:alchemy_language, :klingon)) + end + + it "can manage a site they can access" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.to be_able_to(:manage, Alchemy::UserWithLanguages.new(user).accessible_sites.first) + end + + it "cannot manage a site they cannot access" do + user = create(:alchemy_dummy_user, :as_editor, languages: create_list(:alchemy_language, 1, :german)) + + is_expected.to be_able_to(:index, create(:alchemy_site, host: "abc.def")) end end diff --git a/spec/views/admin/pictures/show_spec.rb b/spec/views/admin/pictures/show_spec.rb index 60caee1bf6..8da7f9b33a 100644 --- a/spec/views/admin/pictures/show_spec.rb +++ b/spec/views/admin/pictures/show_spec.rb @@ -19,6 +19,7 @@ end before do + allow(view).to receive(:can?).and_return(true) allow(view).to receive(:admin_picture_path).and_return("/path") allow(view).to receive(:edit_admin_page_path).and_return("/path") allow(view).to receive(:render_message)