Skip to content

Commit 0b92786

Browse files
authored
Merge pull request #3685 from mlibrary/HELIO-4726/counter-redirect-to-scholarlyiq
HELIO-4726 Forward users from /counter_reports to scholarlyiq
2 parents c407ea4 + 6a4d20c commit 0b92786

6 files changed

+134
-9
lines changed

app/controllers/counter_reports_controller.rb

+6-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ def platforms
3232
def index
3333
return render if @counter_report_service.present?
3434

35-
render 'counter_reports/without_customer_id/index'
35+
config = Rails.root.join('config', 'scholarlyiq.yml')
36+
if Flipflop.scholarlyiq_counter_redirect? && File.exist?(config)
37+
redirect_to ScholarlyiqRedirectUrlService.encrypted_url(config, @institutions)
38+
else
39+
render 'counter_reports/without_customer_id/index'
40+
end
3641
end
3742

3843
def edit
@@ -110,12 +115,8 @@ def set_counter_report
110115
def set_presses_and_institutions
111116
return if @counter_report_service.present?
112117

113-
# HELIO-3526 press admins (and editors, analysts or other authed and prived users) will no longer
114-
# access reports through this controller, but instead through the dashboard and
115-
# hyrax/admin/stats_controller.rb
116118
@institutions = current_institutions.sort_by(&:name)
117119
@presses = Press.order(:name)
118-
119120
if @institutions.empty? || @presses.empty?
120121
@skip_footer = true
121122
render 'counter_reports/unauthorized', status: :unauthorized
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
class ScholarlyiqRedirectUrlService
4+
def self.encrypted_url(config, institutions)
5+
yaml = YAML.safe_load(File.read(config))
6+
url = yaml["siq_portal_url"]
7+
secret = yaml["siq_shared_secret"] # AES-128-GCM requires a 16-byte key
8+
iv = SecureRandom.random_bytes(12) # AES-128-GCM require a 12 byte nonce
9+
10+
cipher = OpenSSL::Cipher.new("aes-128-gcm")
11+
cipher.encrypt
12+
cipher.key = secret
13+
cipher.iv = iv
14+
15+
payload = {}
16+
payload["siteIds"] = []
17+
institutions.each do |inst|
18+
payload["siteIds"] << inst.identifier
19+
end
20+
21+
encrypted_payload = cipher.update(payload.to_json) + cipher.final
22+
auth_tag = cipher.auth_tag
23+
24+
encoded_encrypted_payload = Base64.strict_encode64(encrypted_payload + auth_tag)
25+
encoded_nonce = Base64.strict_encode64(iv)
26+
27+
url_encoded_encrypted_payload = CGI.escape(encoded_encrypted_payload)
28+
url_encoded_nonce = CGI.escape(encoded_nonce)
29+
30+
token = "#{url_encoded_nonce}:#{url_encoded_encrypted_payload}"
31+
url + "?token=#{token}"
32+
end
33+
end

config/features.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
# Add fulcrum specific flipflop features here
4+
5+
Flipflop.configure do
6+
feature :scholarlyiq_counter_redirect,
7+
default: false,
8+
description: "Redirect known users from /counter_report to scholarlyiq"
9+
end

config/scholarlyiq.yml.sample

+4
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ Bucket: "s3 bucket"
44
BucketRegion: "s3 bucket region"
55
AwsAccessKeyId: 123456789XYZ
66
AwsSecretAccessKey: AwsSecretAccessKeyAwsSecretAccessKey
7+
8+
# HELIO-4726 for redirecting users to SIQ to see their reports
9+
siq_portal_url: 'scholarlyiq.com'
10+
siq_shared_secret: 'some-shared-secret'

spec/requests/counter_reports_spec.rb

+25-4
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,31 @@
154154
end
155155

156156
describe "GET /counter_reports" do
157-
it do
158-
get counter_reports_path
159-
expect(response).to have_http_status(:ok)
160-
expect(response).to render_template(:index)
157+
context "with scholarlyiq redirect" do
158+
before do
159+
allow(Flipflop).to receive(:scholarlyiq_counter_redirect?).and_return(true)
160+
allow(File).to receive(:exist?).and_return(true)
161+
allow(File).to receive(:read).and_return(true)
162+
allow(YAML).to receive(:safe_load).and_return(yaml)
163+
end
164+
165+
let(:yaml) { { 'siq_portal_url' => 'http://test.scholarlyiq.com', 'siq_shared_secret' => '0123456789123456' } }
166+
it do
167+
get counter_reports_path
168+
expect(response).to have_http_status(:found)
169+
end
170+
end
171+
172+
context "without scholarlyiq redirect" do
173+
before do
174+
allow(Flipflop).to receive(:scholarlyiq_counter_redirect?).and_return(false)
175+
end
176+
177+
it do
178+
get counter_reports_path
179+
expect(response).to have_http_status(:ok)
180+
expect(response).to render_template(:index)
181+
end
161182
end
162183
end
163184

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe ScholarlyiqRedirectUrlService do
6+
describe "#encrypted_url" do
7+
let(:institutions) do
8+
[
9+
create(:institution, identifier: "1"),
10+
create(:institution, identifier: "10")
11+
]
12+
end
13+
14+
let(:shared_secret) { "0123456789123456" } # 16 byte shared secret
15+
let(:nonce) { "012345678912" } # 12 byte nonce
16+
let(:yaml) { { "siq_portal_url" => "http://test.scholarlyiq.com", "siq_shared_secret" => shared_secret } }
17+
let(:config) { double("config") }
18+
19+
before do
20+
allow(File).to receive(:read).and_return(true)
21+
allow(YAML).to receive(:safe_load).and_return(yaml)
22+
allow(SecureRandom).to receive(:random_bytes).with(12).and_return(nonce)
23+
end
24+
25+
it "correctly builds the encrypted url" do
26+
# Just a check that the payload we encrpyted decrypts correctly
27+
redirect_url = described_class.encrypted_url(config, institutions)
28+
29+
# Extract the token from the URL
30+
token_param = redirect_url.match(/token=([^&]+)/)[1]
31+
32+
# Split the token into nonce and encrypted payload parts
33+
url_encoded_nonce, url_encoded_encrypted_payload = token_param.split(':')
34+
35+
# URL-decode and Base64 decode the nonce and the encrypted payload
36+
iv = Base64.decode64(CGI.unescape(url_encoded_nonce))
37+
encrypted_payload_with_auth_tag = Base64.decode64(CGI.unescape(url_encoded_encrypted_payload))
38+
39+
# Extract the ciphertext and authentication tag
40+
auth_tag = encrypted_payload_with_auth_tag[-16..-1]
41+
encrypted_payload = encrypted_payload_with_auth_tag[0..-17]
42+
43+
# Create a Cipher for AES-128-GCM decryption
44+
cipher = OpenSSL::Cipher.new('aes-128-gcm')
45+
cipher.decrypt
46+
cipher.key = shared_secret
47+
cipher.iv = iv # use the decoded 12-byte nonce
48+
cipher.auth_tag = auth_tag
49+
50+
# Decrypt the payload
51+
decrypted_payload = cipher.update(encrypted_payload) + cipher.final
52+
53+
expect(iv).to eq nonce
54+
expect(JSON.parse(decrypted_payload)).to eq({ "siteIds" => ["1", "10"] })
55+
end
56+
end
57+
end

0 commit comments

Comments
 (0)