Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gh-134] Various Signed URLs generation updates #162

Merged
merged 11 commits into from
Apr 27, 2024
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@

### Fixed

* Throw `AuthError` if current public key or secret key config are empty when accessing any of the APIs.
* Throw `AuthError` if current public key or secret key config are empty when accessing any of the APIs.
* Fixed typo in `AkamaiGenerator` class name from `AmakaiGenerator`.

### Added
* Added an optional `wildcard` boolean parameter to the `generate_url` method of `AkamaiGenerator`.

## 4.4.0 — 2024-03-09

Expand Down
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -838,15 +838,29 @@ More examples and options can be found [here](https://uploadcare.com/docs/transf

You can use custom domain and CDN provider to deliver files with authenticated URLs (see [original documentation](https://uploadcare.com/docs/security/secure_delivery/)).

To generate authenticated URL from the library, you should choose `Uploadcare::SignedUrlGenerators::AmakaiGenerator` (or create your generator implementation):
To generate authenticated URL from the library, you should choose `Uploadcare::SignedUrlGenerators::AkamaiGenerator` (or create your own generator implementation):

```ruby
generator = Uploadcare::SignedUrlGenerators::AmakaiGenerator.new(cdn_host: 'example.com', secret_key: 'secret_key'). Optional parameters: ttl: 300, algorithm: 'sha256'
generator = Uploadcare::SignedUrlGenerators::AkamaiGenerator.new(cdn_host: 'example.com', secret_key: 'secret_key')
# Optional parameters: ttl: 300, algorithm: 'sha256'
generator.generate_url(uuid, acl = optional)

generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3") ->
https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649405263~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=a989cae5342f17013677f5a0e6577fc5594cc4e238fb4c95eda36634eb47018b
generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", '/*/') ->
https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649405263~acl=/*/~hmac=3ce1152c6af8864b36d4dc721f08ca3cf0b3a20278d7f849e82c6c930d48ccc1
generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3")
# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649405263~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=a989cae5342f17013677f5a0e6577fc5594cc4e238fb4c95eda36634eb47018b

# You can pass in ACL as a second parameter to generate_url. See https://uploadcare.com/docs/security/secure-delivery/#authenticated-urls for supported acl formats
generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", '/*/')
# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649405263~acl=/*/~hmac=3ce1152c6af8864b36d4dc721f08ca3cf0b3a20278d7f849e82c6c930d48ccc1

# Optionally you can use wildcard: true to generate a wildcard acl token
generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", wildcard: true)
# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1714233449~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/*~hmac=a568ee2a85dd90a8a8a1ef35ea0cc0ef0acb84fe81990edd3a06eacf10a52b4e

# You can also pass in a custom ttl and algorithm to AkamaiGenerator
generator = Uploadcare::SignedUrlGenerators::AkamaiGenerator.new(cdn_host: 'example.com', secret_key: 'secret_key', ttl: 10)
generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3")
# This generates a URL that expires in 10 seconds
# https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1714233277~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=f25343104aeced3004d2cc4d49807d8d7c732300b54b154c319da5283a871a71
```
## Useful links

Expand Down
2 changes: 1 addition & 1 deletion lib/uploadcare.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
require 'api/api'

# SignedUrlGenerators
require 'signed_url_generators/amakai_generator'
require 'signed_url_generators/akamai_generator'
require 'signed_url_generators/base_generator'

# Ruby wrapper for Uploadcare API
Expand Down
2 changes: 1 addition & 1 deletion lib/uploadcare/entity/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class File < Entity
attr_entity(*RESPONSE_PARAMS)

def datetime_stored
Uploadcare.config.logger&.warn 'datetime_stored property has be deprecated, and will be removed without a replacement in future.' # rubocop:disable Layout/LineLength
Uploadcare.config.logger&.warn 'datetime_stored property has been deprecated, and will be removed without a replacement in future.' # rubocop:disable Layout/LineLength
@entity.datetime_stored
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@

module Uploadcare
module SignedUrlGenerators
class AmakaiGenerator < Uploadcare::SignedUrlGenerators::BaseGenerator
class AkamaiGenerator < Uploadcare::SignedUrlGenerators::BaseGenerator
UUID_REGEX = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
TEMPLATE = 'https://{cdn_host}/{uuid}/?token=exp={expiration}{delimiter}acl={acl}{delimiter}hmac={token}'

def generate_url(uuid, acl = uuid)
def generate_url(uuid, acl = uuid, wildcard: false)
raise ArgumentError, 'Must contain valid UUID' unless valid?(uuid)

formatted_acl = build_acl(uuid, acl, wildcard: wildcard)
expire = build_expire
signature = build_signature(expire, acl)
signature = build_signature(expire, formatted_acl)

TEMPLATE.gsub('{delimiter}', delimiter)
.sub('{cdn_host}', sanitized_string(cdn_host))
.sub('{uuid}', sanitized_string(uuid))
.sub('{acl}', "/#{sanitized_string(acl)}/")
.sub('{acl}', formatted_acl)
.sub('{expiration}', expire)
.sub('{token}', signature)
end
Expand All @@ -32,13 +33,22 @@ def delimiter
'~'
end

def build_acl(uuid, acl, wildcard: false)
if wildcard
"/#{sanitized_string(uuid)}/*"
else
"/#{sanitized_string(acl)}/"
end
end

def build_expire
(Time.now.to_i + ttl).to_s
end

def build_signature(expire, acl)
signature = ["exp=#{expire}", "acl=/#{sanitized_string(acl)}/"].join(delimiter)
OpenSSL::HMAC.hexdigest(algorithm, secret_key, signature)
signature = ["exp=#{expire}", "acl=#{acl}"].join(delimiter)
secret_key_bin = Array(secret_key.gsub(/\s/, '')).pack('H*')
OpenSSL::HMAC.hexdigest(algorithm, secret_key_bin, signature)
end

# rubocop:disable Style/SlicingWithRange
Expand Down
4 changes: 2 additions & 2 deletions spec/uploadcare/entity/file_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ module Entity
file = File.new(url: url)
logger = Uploadcare.config.logger
file.load
allow(logger).to receive(:warn).with('datetime_stored property has be deprecated, and will be removed without a replacement in future.')
allow(logger).to receive(:warn).with('datetime_stored property has been deprecated, and will be removed without a replacement in future.')
datetime_stored = file.datetime_stored
expect(logger).to have_received(:warn).with('datetime_stored property has be deprecated, and will be removed without a replacement in future.')
expect(logger).to have_received(:warn).with('datetime_stored property has been deprecated, and will be removed without a replacement in future.')
expect(datetime_stored).not_to be_nil
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# frozen_string_literal: true

require 'spec_helper'
require 'signed_url_generators/amakai_generator'
require 'signed_url_generators/akamai_generator'

module Uploadcare
RSpec.describe SignedUrlGenerators::AmakaiGenerator do
RSpec.describe SignedUrlGenerators::AkamaiGenerator do
subject { described_class.new(cdn_host: 'example.com', secret_key: secret_key) }

let(:default_ttl) { 300 }
Expand All @@ -20,7 +20,7 @@ module Uploadcare

context 'when acl not present' do
it 'returns correct url' do
expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=a82d0068adeb2fc5ecf87e7210fe537d234940807725f982dd6c776cbd24df3a'
expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/~hmac=d8b4919d595805fd8923258bb647065b7d7201dad8f475d6f5c430e3bffa8122'
expect(subject.generate_url(uuid)).to eq expected_url
end
end
Expand All @@ -29,15 +29,15 @@ module Uploadcare
let(:uuid) { "#{super()}/-/resize/640x/other/transformations/" }

it 'returns correct url' do
expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/-/resize/640x/other/transformations/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/-/resize/640x/other/transformations/~hmac=7517f479d4413225b48b91f51b83c13f26c1e690adc72dff4dcf627e23d7f676'
expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/-/resize/640x/other/transformations/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/-/resize/640x/other/transformations/~hmac=64dd1754c71bf194fcc81d49c413afeb3bbe0e6d703ed4c9b30a8a48c1782f53'
expect(subject.generate_url(uuid)).to eq expected_url
end
end

context 'when acl present' do
it 'returns correct url' do
acl = '/*/'
expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/*/~hmac=06ab92c7ae863f7d6375d9fd28aa02bd7ca1cab9a10a14653b6cf6ea4f5170b2'
expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/*/~hmac=984914950bccbfe22f542aa1891300fb2624def1208452335fc72520c934c4c3'
expect(subject.generate_url(uuid, acl)).to eq expected_url
end
end
Expand All @@ -47,6 +47,13 @@ module Uploadcare
expect { subject.generate_url(SecureRandom.hex) }.to raise_error ArgumentError
end
end

context 'when wildcard is true' do
it 'returns correct url' do
expected_url = 'https://example.com/a7d5645e-5cd7-4046-819f-a6a2933bafe3/?token=exp=1649343900~acl=/a7d5645e-5cd7-4046-819f-a6a2933bafe3/*~hmac=6f032220422cdaea5fe0b58f9dcf681269591bb5d1231aa1c4a38741d7cc2fe5'
expect(subject.generate_url(uuid, nil, wildcard: true)).to eq expected_url
end
end
end
end
end
Loading