Skip to content

Commit

Permalink
🎁 Adding logic for handling user signin
Browse files Browse the repository at this point in the history
With this change, we're adding the logic for handling a user
authenticating:

- namely expiring any authorizations that are "expired"
- re-authorizing any work_pids that were provided as part of
  authentication.

Related to:

- #633
- #647
  • Loading branch information
jeremyf committed Aug 7, 2023
1 parent 98cc8aa commit 68f644c
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 2 deletions.
26 changes: 24 additions & 2 deletions app/models/work_authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ def initialize(user:, work_pid:)
# This will be a non-ActiveRecord resource
validates :work_pid, presence: true

##
# @param user [User]
# @param authorize_until [Time] authorize the given work_pid(s) until this point in time.
# @param revoke_expirations_before [Time] expire all authorizations that have expires_at less than or equal to this parameter.
# @param work_pid [String, Array<String>]
def self.handle_signin_for!(user:, authorize_until: 1.day.from_now, work_pid: nil, revoke_expirations_before: Time.zone.now)
# Maybe we get multiple pids; let's handle that accordingly
Array.wrap(work_pid).each do |pid|
begin
authorize!(user: user, work_pid: pid, expires_at: authorize_until)
rescue WorkNotFoundError
Rails.logger.info("Unable to find work_pid of #{pid.inspect}.")
end
end

# We re-authorized the above work_pid, so it should not be in this query.
where("user_id = :user_id AND expires_at <= :expires_at", user_id: user.id, expires_at: revoke_expirations_before).pluck(:work_pid).each do |pid|
revoke!(user: user, work_pid: pid)
end
end

##
# Grant the given :user permission to read the work associated with the given :work_pid.
#
Expand All @@ -33,13 +54,14 @@ def initialize(user:, work_pid:)
#
# @see .revoke!
# rubocop:disable Rails/FindBy
def self.authorize!(user:, work_pid:)
def self.authorize!(user:, work_pid:, expires_at: 1.day.from_now)
work = ActiveFedora::Base.where(id: work_pid).first
raise WorkNotFoundError.new(user: user, work_pid: work_pid) unless work

transaction do
authorization = find_or_create_by!(user_id: user.id, work_pid: work.id)
authorization.update!(work_title: work.title)
authorization.update!(work_title: work.title, expires_at: expires_at)

work.set_read_users([user.user_key], [user.user_key])
work.save!
end
Expand Down
31 changes: 31 additions & 0 deletions spec/models/work_authorization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,40 @@

RSpec.describe WorkAuthorization, type: :model do
let(:work) { FactoryBot.create(:generic_work) }
let(:other_work) { FactoryBot.create(:generic_work) }
let(:borrowing_user) { FactoryBot.create(:user) }
let(:ability) { ::Ability.new(borrowing_user) }

describe '.handle_signin_for!' do
context 'when given a work_pid' do
it 'will re-authorize the given work_pid and expire non-specified work_ids' do
described_class.authorize!(user: borrowing_user, work_pid: work.id, expires_at: 1.day.ago)
described_class.authorize!(user: borrowing_user, work_pid: other_work.id, expires_at: 1.day.ago)

expect do
expect do
described_class.handle_signin_for!(user: borrowing_user, work_pid: work.id, authorize_until: 1.day.from_now)
end.not_to change { ::Ability.new(borrowing_user).can?(:read, work.id) }.from(true)
end.to change { ::Ability.new(borrowing_user).can?(:read, other_work.id) }.from(true).to(false)
end
end

context 'when not given a work_pid' do
it 'will de-authorize all authorizations that have expired but not those that have not expired' do
# Note: This one is expiring in the future
described_class.authorize!(user: borrowing_user, work_pid: work.id, expires_at: 2.days.from_now)
# Note: We'll be expiring this one.
described_class.authorize!(user: borrowing_user, work_pid: other_work.id, expires_at: 1.day.ago)

expect do
expect do
described_class.handle_signin_for!(user: borrowing_user, revoke_expirations_before: Time.zone.now)
end.not_to change { ::Ability.new(borrowing_user).can?(:read, work.id) }.from(true)
end.to change { ::Ability.new(borrowing_user).can?(:read, other_work.id) }.from(true).to(false)
end
end
end

describe '.authorize!' do
it 'gives the borrowing user the ability to "read" the work' do
# We re-instantiate an ability class because CanCan caches many of the ability checks. By
Expand Down

0 comments on commit 68f644c

Please sign in to comment.