Skip to content

Commit

Permalink
Merge pull request #3680 from mlibrary/HELIO-4561/editor_ebook_share_…
Browse files Browse the repository at this point in the history
…link_draft_sibling_access

HELIO-4561 - share link access to draft Monograph
  • Loading branch information
sethaj authored Aug 27, 2024
2 parents 96ac396 + 025fcf6 commit cb6c725
Show file tree
Hide file tree
Showing 16 changed files with 575 additions and 96 deletions.
14 changes: 7 additions & 7 deletions app/controllers/e_pubs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,16 @@ def download_interval # rubocop:disable Metrics/CyclomaticComplexity, Metrics/Pe
def share_link
return head :no_content unless @policy.show?

subdomain = @presenter.parent.subdomain
subdomain = @parent_presenter.subdomain
if Press.where(subdomain: subdomain).first&.allow_share_links?
expire = Time.now.to_i + 28 * 24 * 3600 # 28 days in seconds
token = JsonWebToken.encode(data: @noid, exp: expire)
token = JsonWebToken.encode(data: @parent_presenter.id, exp: expire)
ShareLinkLog.create(ip_address: request.ip,
institution: current_institutions.map(&:name).join("|"),
press: subdomain,
user: current_actor.email,
title: @presenter.parent.title,
noid: @presenter.id,
title: @parent_presenter.title,
noid: @parent_presenter.id,
token: token,
action: 'create')
render plain: Rails.application.routes.url_helpers.epub_url(@noid, share: token)
Expand Down Expand Up @@ -191,7 +191,7 @@ def log_share_link_use
press: @subdomain,
user: current_actor.email,
title: @parent_presenter.title,
noid: @noid,
noid: @parent_presenter.id,
token: @share_link,
action: 'use')
end
Expand All @@ -200,8 +200,8 @@ def valid_share_link?
if @share_link.present?
begin
decoded = JsonWebToken.decode(@share_link)
return true if decoded[:data] == @noid
rescue JWT::ExpiredSignature
return true if decoded[:data] == @noid || decoded[:data] == @parent_presenter.id
rescue JWT::ExpiredSignature, JWT::VerificationError
return false
end
end
Expand Down
28 changes: 26 additions & 2 deletions app/controllers/monograph_catalog_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,37 @@ def item_identifier_for_irus_analytics

private

def valid_share_link?
return @valid_share_link if @valid_share_link.present?

share_link = params[:share] || session[:share_link]
session[:share_link] = share_link

@valid_share_link = if share_link.present?
begin
decoded = JsonWebToken.decode(share_link)
true if decoded[:data] == @monograph_presenter&.id
rescue JWT::ExpiredSignature, JWT::VerificationError
false
end
else
false
end
end

instrument_method
def load_presenter
retries ||= 0
monograph_id = params[:monograph_id] || params[:id]
@monograph_presenter = Hyrax::PresenterFactory.build_for(ids: [monograph_id], presenter_class: Hyrax::MonographPresenter, presenter_args: current_ability).first
raise PageNotFoundError if @monograph_presenter.nil?
raise CanCan::AccessDenied unless current_ability&.can?(:read, @monograph_presenter)

# We don't "sell"/protect the Monograph catalog page. This line is the one and only place where the Monograph's...
# draft (restricted) status can prevent it being universally seen.
# Share links being used to "expose" the Monograph page are only for editors allowing, e.g. authors to easily...
# review draft content. We can assume the Monograph is draft if the first condition is false.
raise CanCan::AccessDenied unless current_ability&.can?(:read, @monograph_presenter) || valid_share_link?

rescue RSolr::Error::ConnectionRefused, RSolr::Error::Http => e
Rails.logger.error(%Q|[RSOLR ERROR TRY:#{retries}] #{e} #{e.backtrace.join("\n")}|)
retries += 1
Expand All @@ -128,7 +152,7 @@ def monograph_auth_for
@ebook_download_presenter = EBookDownloadPresenter.new(@monograph_presenter, current_ability, current_actor)
# The monograph catalog page is completely user-facing, apart from a small admin menu. The "Read" button should...
# never show up if there is no published ebook for CSB to use! This is important for the "Forthcoming" workflow.
@show_read_button = @monograph_presenter.reader_ebook? && @monograph_presenter&.reader_ebook['visibility_ssi'] == 'open'
@show_read_button = @monograph_presenter.reader_ebook? && (@monograph_presenter&.reader_ebook['visibility_ssi'] == 'open' || valid_share_link?)
@disable_read_button = disable_read_button?
end

Expand Down
21 changes: 21 additions & 0 deletions app/models/monograph_search_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class MonographSearchBuilder < ::SearchBuilder
:filter_out_tombstones
]

# this prevents the Solr request from filtering draft documents out, given an anonymous user using a share link,...
# otherwise the following would be added to `fq`, removing such documents from the response completely:
# ({!terms f=edit_access_group_ssim}public) OR ({!terms f=discover_access_group_ssim}public) OR ({!terms f=read_access_group_ssim}public)"
self.default_processor_chain -= [:add_access_controls_to_solr_params] if :valid_share_link?

instrument_method
def filter_by_monograph_id(solr_parameters)
id = monograph_id(blacklight_params)
Expand Down Expand Up @@ -48,6 +53,22 @@ def filter_out_tombstones(solr_parameters)
solr_parameters[:fq] << "-tombstone_ssim:[* TO *]"
end

def valid_share_link?
share_link = blacklight_params[:share] || session[:share_link]
session[:share_link] = share_link

if share_link.present?
begin
decoded = JsonWebToken.decode(share_link)
return true if decoded[:data] == @monograph_presenter&.id
rescue JWT::ExpiredSignature
false
end
else
false
end
end

private

def monograph_id(blacklight_params)
Expand Down
14 changes: 7 additions & 7 deletions app/overrides/hyrax/downloads_controller_overrides.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,29 +104,29 @@ def allow_download?

# HELIO-4501 Override to use the Hyrax 3.4 version of this with no workflow related code.
def authorize_download!
return true if authorize_embeds_for_epub_share_link?
return true if authorize_thumbs_and_embeds_for_share_link?

authorize! :download, params[asset_param_key]
rescue CanCan::AccessDenied
unauthorized_image = Rails.root.join("app", "assets", "images", "unauthorized.png")
send_file unauthorized_image, status: :unauthorized
end

def authorize_embeds_for_epub_share_link?
def authorize_thumbs_and_embeds_for_share_link?
# adding some logic to allow *draft* FileSet "downloads" to work when a session holds the sibling EPUB's share link.
# This is specifically so that draft embedded video, jpeg (video poster), audio and animated gif resources will display in CSB.
# Images will work anyway seeing as RIIIF tiles get served regardless of the originating FileSet's publication status.

if presenter.visibility == 'restricted' && presenter&.parent&.epub? && (jpeg? || video? || sound? || animated_gif? || closed_captions? || visual_descriptions?)
# I think the link could only be in the session here, but will check for `params[:share]` anyway
share_link = params[:share] || session[:share_link]
session[:share_link] = share_link
share_link = params[:share] || session[:share_link]
session[:share_link] = share_link if share_link.present?

# note we're *not* authorizing *all* FileSet downloads from Fedora here, just those tied to display
if thumbnail? || jpeg? || video? || sound? || animated_gif? || closed_captions? || visual_descriptions?
if share_link.present?
begin
decoded = JsonWebToken.decode(share_link)

return true if decoded[:data] == presenter&.parent&.epub_id
return true if decoded[:data] == presenter&.parent&.id
rescue JWT::ExpiredSignature
false
end
Expand Down
24 changes: 23 additions & 1 deletion app/overrides/hyrax/file_sets_controller_overrides.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,29 @@ def reindex
# See hyrax, https://github.com/samvera/hyrax/commit/cb21570fadcea0b8d1dfd0b7cffecf5135c1ea76
# See hyrax, https://github.com/samvera/hyrax/commit/1efe93929285985751cc270675c243f628cf31ca
def presenter
@presenter ||= show_presenter.new(curation_concern_document, current_ability, request)
@presenter ||= if valid_share_link?
@share_link_presenter
else
show_presenter.new(curation_concern_document, current_ability, request)
end
end

def valid_share_link?
return @valid_share_link if @valid_share_link.present?

share_link = params[:share] || session[:share_link]
session[:share_link] = share_link

@valid_share_link = if share_link.present?
@share_link_presenter = show_presenter.new(::SolrDocument.find(params[:id]), current_ability, request)

begin
decoded = JsonWebToken.decode(share_link)
true if decoded[:data] == @share_link_presenter&.parent&.id
rescue JWT::ExpiredSignature
false
end
end
end
end)
end
8 changes: 4 additions & 4 deletions app/presenters/concerns/common_work_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ def non_representative_file_set_ids # rubocop:disable Metrics/CyclomaticComplexi
@non_representative_file_set_ids = file_sets_ids
end

def assets?
ordered_file_sets_ids.present?
def assets?(valid_share_link = false)
ordered_file_sets_ids(valid_share_link).present?
end

def ordered_file_sets_ids # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
def ordered_file_sets_ids(valid_share_link = false) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
return @ordered_file_sets_ids if @ordered_file_sets_ids
file_sets_ids = []
ordered_member_docs.each do |doc|
next if doc['has_model_ssim'] != ['FileSet'].freeze
next if doc.id == representative_id
next if featured_representatives.map(&:file_set_id).include?(doc.id)
next if doc['visibility_ssi'] != 'open' && !current_ability&.can?(:read, doc.id)
next if doc['visibility_ssi'] != 'open' && !(current_ability&.can?(:read, doc.id) || valid_share_link)

file_sets_ids.append doc.id
end
Expand Down
2 changes: 1 addition & 1 deletion app/presenters/concerns/embed_code_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def embeddable_type?
end

def allow_embed?
current_ability.platform_admin?
current_ability&.platform_admin?
end

def embed_code
Expand Down
2 changes: 1 addition & 1 deletion app/views/monograph_catalog/_index_monograph.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@
</div><!-- /.monograph-info-epub -->

<!-- RESOURCES -->
<% if @monograph_presenter.assets? %>
<% if @monograph_presenter.assets?(@valid_share_link) %>
<div class="row monograph-assets">
<div class="col-sm-12">
<h2>Resources</h2>
Expand Down
12 changes: 6 additions & 6 deletions spec/controllers/e_pubs_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -677,13 +677,13 @@

context "A restricted epub" do
let(:valid_share_token) do
JsonWebToken.encode(data: file_set.id, exp: Time.now.to_i + 48 * 3600)
JsonWebToken.encode(data: monograph.id, exp: Time.now.to_i + 28 * 24 * 3600)
end
let(:expired_share_token) do
JsonWebToken.encode(data: file_set.id, exp: Time.now.to_i - 1000)
JsonWebToken.encode(data: monograph.id, exp: Time.now.to_i - 1000)
end
let(:wrong_share_token) do
JsonWebToken.encode(data: 'wrongnoid', exp: Time.now.to_i + 48 * 3600)
JsonWebToken.encode(data: 'wrongnoid', exp: Time.now.to_i + 28 * 24 * 3600)
end
let(:parent) { Sighrax.from_noid(monograph.id) }
let(:epub) { Sighrax.from_noid(file_set.id) }
Expand All @@ -701,7 +701,7 @@
expect(ShareLinkLog.last.action).to eq 'use'
expect(ShareLinkLog.last.user).to be_nil
expect(ShareLinkLog.last.title).to eq monograph.title.first
expect(ShareLinkLog.last.noid).to eq file_set.id
expect(ShareLinkLog.last.noid).to eq monograph.id
expect(ShareLinkLog.last.token).to eq valid_share_token
end

Expand Down Expand Up @@ -735,7 +735,7 @@
expect(ShareLinkLog.last.action).to eq 'use'
expect(ShareLinkLog.last.user).to eq user.email
expect(ShareLinkLog.last.title).to eq monograph.title.first
expect(ShareLinkLog.last.noid).to eq file_set.id
expect(ShareLinkLog.last.noid).to eq monograph.id
expect(ShareLinkLog.last.token).to eq valid_share_token
end

Expand Down Expand Up @@ -793,7 +793,7 @@
it 'returns a share link with a valid JSON webtoken and logs the creation' do
get :share_link, params: { id: '222222222' }
expect(response).to have_http_status(:success)
expect(response.body).to eq "http://test.host/epubs/222222222?share=#{JsonWebToken.encode(data: '222222222', exp: now.to_i + share_link_expiration_time)}"
expect(response.body).to eq "http://test.host/epubs/222222222?share=#{JsonWebToken.encode(data: '111111111', exp: now.to_i + share_link_expiration_time)}"
expect(ShareLinkLog.count).to eq 1
expect(ShareLinkLog.last.action).to eq 'create'
end
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/hyrax/downloads_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@
}

let(:valid_share_token) do
JsonWebToken.encode(data: draft_epub_file_set.id, exp: Time.now.to_i + 48 * 3600)
JsonWebToken.encode(data: monograph.id, exp: Time.now.to_i + 28 * 24 * 3600)
end

before do
Expand Down
22 changes: 22 additions & 0 deletions spec/controllers/hyrax/file_sets_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,28 @@
expect(response).to redirect_to(Hyrax::Engine.routes.url_helpers.download_path(file_set.id))
expect(response).to have_http_status(:found) # 302 Found
end

context 'draft FileSet with an anonymous user' do
before { sign_out user }

it 'Redirects to login' do
get :show, params: { id: file_set.id }
expect(response).to redirect_to('/login?locale=en')
expect(response).to have_http_status(:found) # 302 Found
end

context 'Using a share link for the parent Monograph' do
let(:valid_share_token) do
JsonWebToken.encode(data: monograph.id, exp: Time.now.to_i + 28 * 24 * 3600)
end

it 'succeeds' do
get :show, params: { id: file_set.id, share: valid_share_token }
expect(response).to have_http_status(:success)
expect(response).to render_template('show')
end
end
end
end

describe "#destroy" do
Expand Down
26 changes: 26 additions & 0 deletions spec/controllers/monograph_catalog_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@
get :index, params: { id: monograph.id }
expect(assigns(:show_read_button)).to eq false
end

context 'with a valid share link' do
let(:valid_share_token) do
JsonWebToken.encode(data: monograph.id, exp: Time.now.to_i + 28 * 24 * 3600)
end

it "shows the read button" do
get :index, params: { id: monograph.id, share: valid_share_token }
expect(assigns(:show_read_button)).to eq true
end
end
end

context 'public ebook FeaturedRepresentative FileSet' do
Expand Down Expand Up @@ -204,6 +215,21 @@
it 'redirects to login page' do
expect(response).to redirect_to(new_user_session_path)
end

context 'with a valid share link' do
let(:valid_share_token) do
JsonWebToken.encode(data: monograph.id, exp: Time.now.to_i + 28 * 24 * 3600)
end

before do
get :index, params: { id: monograph.id, share: valid_share_token }
end

it 'response is successful' do
expect(response).to be_successful
expect(response).to render_template('monograph_catalog/index')
end
end
end

context 'logged-in read user (depositor)' do
Expand Down
Loading

0 comments on commit cb6c725

Please sign in to comment.