|
| 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