-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3685 from mlibrary/HELIO-4726/counter-redirect-to…
…-scholarlyiq HELIO-4726 Forward users from /counter_reports to scholarlyiq
- Loading branch information
Showing
6 changed files
with
134 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# frozen_string_literal: true | ||
|
||
class ScholarlyiqRedirectUrlService | ||
def self.encrypted_url(config, institutions) | ||
yaml = YAML.safe_load(File.read(config)) | ||
url = yaml["siq_portal_url"] | ||
secret = yaml["siq_shared_secret"] # AES-128-GCM requires a 16-byte key | ||
iv = SecureRandom.random_bytes(12) # AES-128-GCM require a 12 byte nonce | ||
|
||
cipher = OpenSSL::Cipher.new("aes-128-gcm") | ||
cipher.encrypt | ||
cipher.key = secret | ||
cipher.iv = iv | ||
|
||
payload = {} | ||
payload["siteIds"] = [] | ||
institutions.each do |inst| | ||
payload["siteIds"] << inst.identifier | ||
end | ||
|
||
encrypted_payload = cipher.update(payload.to_json) + cipher.final | ||
auth_tag = cipher.auth_tag | ||
|
||
encoded_encrypted_payload = Base64.strict_encode64(encrypted_payload + auth_tag) | ||
encoded_nonce = Base64.strict_encode64(iv) | ||
|
||
url_encoded_encrypted_payload = CGI.escape(encoded_encrypted_payload) | ||
url_encoded_nonce = CGI.escape(encoded_nonce) | ||
|
||
token = "#{url_encoded_nonce}:#{url_encoded_encrypted_payload}" | ||
url + "?token=#{token}" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# frozen_string_literal: true | ||
|
||
# Add fulcrum specific flipflop features here | ||
|
||
Flipflop.configure do | ||
feature :scholarlyiq_counter_redirect, | ||
default: false, | ||
description: "Redirect known users from /counter_report to scholarlyiq" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
|
||
RSpec.describe ScholarlyiqRedirectUrlService do | ||
describe "#encrypted_url" do | ||
let(:institutions) do | ||
[ | ||
create(:institution, identifier: "1"), | ||
create(:institution, identifier: "10") | ||
] | ||
end | ||
|
||
let(:shared_secret) { "0123456789123456" } # 16 byte shared secret | ||
let(:nonce) { "012345678912" } # 12 byte nonce | ||
let(:yaml) { { "siq_portal_url" => "http://test.scholarlyiq.com", "siq_shared_secret" => shared_secret } } | ||
let(:config) { double("config") } | ||
|
||
before do | ||
allow(File).to receive(:read).and_return(true) | ||
allow(YAML).to receive(:safe_load).and_return(yaml) | ||
allow(SecureRandom).to receive(:random_bytes).with(12).and_return(nonce) | ||
end | ||
|
||
it "correctly builds the encrypted url" do | ||
# Just a check that the payload we encrpyted decrypts correctly | ||
redirect_url = described_class.encrypted_url(config, institutions) | ||
|
||
# Extract the token from the URL | ||
token_param = redirect_url.match(/token=([^&]+)/)[1] | ||
|
||
# Split the token into nonce and encrypted payload parts | ||
url_encoded_nonce, url_encoded_encrypted_payload = token_param.split(':') | ||
|
||
# URL-decode and Base64 decode the nonce and the encrypted payload | ||
iv = Base64.decode64(CGI.unescape(url_encoded_nonce)) | ||
encrypted_payload_with_auth_tag = Base64.decode64(CGI.unescape(url_encoded_encrypted_payload)) | ||
|
||
# Extract the ciphertext and authentication tag | ||
auth_tag = encrypted_payload_with_auth_tag[-16..-1] | ||
encrypted_payload = encrypted_payload_with_auth_tag[0..-17] | ||
|
||
# Create a Cipher for AES-128-GCM decryption | ||
cipher = OpenSSL::Cipher.new('aes-128-gcm') | ||
cipher.decrypt | ||
cipher.key = shared_secret | ||
cipher.iv = iv # use the decoded 12-byte nonce | ||
cipher.auth_tag = auth_tag | ||
|
||
# Decrypt the payload | ||
decrypted_payload = cipher.update(encrypted_payload) + cipher.final | ||
|
||
expect(iv).to eq nonce | ||
expect(JSON.parse(decrypted_payload)).to eq({ "siteIds" => ["1", "10"] }) | ||
end | ||
end | ||
end |