scientist-softserv / palni-palci

Other
1 stars 0 forks source link

ReShare - Set up a work lease for authenticated user #633

Open labradford opened 1 year ago

labradford commented 1 year ago

Story

A user is sent to a Hyku and will login using SSO. Once the user is signed in, they will be redirected to Hyku with a URL like this:

[https://vufind.reshare-dev.indexdata.com/east/cdl/auth?[response_type=token]&client_id=myconsortium-hyku&redirect_uri=https://indexdata.com/authorized&scope=BOOKURL[&state=XXX](https://vufind.reshare-dev.indexdata.com/east/cdl/auth?%5Bresponse_type=token%5D&client_id=myconsortium-hyku&redirect_uri=https://indexdata.com/authorized&scope=BOOKURL%5B&state=XXX)

This URL will also need an expiration date/time.

For this ticket, we will need to:

  1. Parse the URL and Scope to find/create the user, determine the work_id, and the purpose of SCOPE param
    • [X] Leverage UCSD's Reading Room logic (alluded to below) to manage read rights

Acceptance Criteria

Testing

To be filled out by developer

Notes

Number 1 is partially blocked by:

labradford commented 1 year ago

Per client we won't be getting a time stamp but will require users to re-login after 24 hours

jeremyf commented 1 year ago

In the UCSD Virtual Reading Room, they have a WorkAuthorization model; this model describes which users have CDL rights to which Works. These rights are revoked via a cron job.

There is an external process that provides a list of each loan request: a persona and a loaned object. From that list, we go through and process the request:

There is a view into the WorkAuthorizations for administrators of the site; they are able to see the WorkAuthorization records and thus see who has been assigned temporary read writes to what titles.

Much of what appears in code looks to be well-tested. The main variance I’d imagine is the initial input of the process. UCSD Virtual Reading Room relies on an external service to determine what authorizations to create. We could introduce a different “service” to look those things up. Or, more directly manually call Processors::NewRightsProcess#authorize within the application. Put another way, there’s a nice and interface in play.

This is something that could be constructed as a gem; in this version I’d recommend that we directly incorporate this into the Hyku, but place all of this in a module, thus allowing easier extraction later.

# frozen_string_literal: true

module Processors
  class NewRightsProcessor
    def self.process_new
      queue = Aeon::Queue.find(Aeon::Queue::NEW_STATUS)
      queue.requests.each do |request|
        request.set_to_processing
        Processors::NewRightsProcessor.new(request).authorize
        request.set_to_active
      end
    end

    def self.revoke_old
      WorkAuthorization.where('updated_at < ?', 1.month.ago).each do |auth|
        params = { work_pid: auth.work_pid, email: auth.user.email, aeon_id: auth.aeon_id }
        request = Processors::NewRightsProcessor.new(params)
        request.revoke
      end
    end

    def initialize(request_attributes)
      @request_attributes = request_attributes
      @work_title = @request_attributes[:itemTitle]
      @work_pid = if ['development'].include? Rails.env
                    DamsObject.last.pid
                  else
                    @request_attributes[:subLocation]
                  end
      @email = @request_attributes[:email].presence || @request_attributes[:username]
    end

    def authorize
      return unless @email.present? && user.valid? && @work_pid.present? && work_obj
      process_request(@request_attributes.id)
      create_work_authorization
      activate_request(@request_attributes.id)
      send_email
    rescue => e # rescue all errors to handle them manually
      work_authorization.update_error 'Unable to Authorize Request'
      raise e
    end

    def revoke
      return unless user && work_obj
      delete_work_authorization
      expire_request(@request_attributes.id)
    rescue => e # rescue all errors to handle them manually
      work_authorization.update_error 'Unable to Revoke Request'
      raise e
    end

    private

    def user
      return @user if @user
      @user = User.where(email: @email).first_or_initialize do |user| # only hits the do on initialize
        user.provider = 'auth_link'
        user.uid = SecureRandom.uuid
        user.ensure_authentication_token
      end
      @user.save
      @user
    end

    def work_obj
      return @work_obj if @work_obj
      @work_obj = DamsObject.where(pid: @work_pid).first if @work_pid
    end

    def work_authorization
      @work_authorization ||= user.work_authorizations.where(work_pid: @work_pid).first_or_create do |authorization|
        authorization.aeon_id = @request_attributes.id
        authorization.work_title = @work_title
      end
    end

    def create_work_authorization
      return unless work_authorization.valid?
      # touch to get the updated authorizations
      # disable rubocop because we run validations before calling .touch
      work_authorization.touch # rubocop:disable SkipsModelValidations
      work_obj.set_read_users([user.user_key], [user.user_key])
      work_obj.save
    end

    def delete_work_authorization
      work_authorization.destroy
      work_obj.set_read_users([], [user.user_key])
      work_obj.save
    end

    def send_email
      AuthMailer.send_link(user).deliver_later
    end

    def process_request(request_id)
      Aeon::Request.find(request_id).set_to_processing
    end

    def expire_request(request_id)
      Aeon::Request.find(request_id).set_to_expired
    end

    def activate_request(request_id)
      Aeon::Request.find(request_id).set_to_active
    end
  end
end
jeremyf commented 1 year ago

Here is the desired local workflow:

One challenge: When you are already logged in and go to a page that is private (e.g. you can’t see) the system notes that this is private and does not prompt you to authenticate.

I'm thinking about creating a development only service that is configured for after authentication to grant access to the work at the original URL. This would allow for continued local testing and help us move forward without all of the SSO work.