class DmCloud::Signing

Public Class Methods

build_digest_struct(base) click to toggle source

Prepare datas for signing Params :

base : contains media id and others for url signing
# File lib/dm_cloud/signing.rb, line 75
def self.build_digest_struct(base)
  result = []
  base.each_pair { |key, value| result << value }
  result.join('')
end
identify(request) click to toggle source

Generate auth token for request from Media Params:

request: A hash of params generated from Media methods and Media::MetaData

Result :

return a string which contain the auth token for the request
<url>?auth=<expires>-<sec>-<nonce>-<md5sum>[-<pub-sec-data>]
# File lib/dm_cloud/signing.rb, line 12
def self.identify(request)
  user_id  = DmCloud.config[:user_key]
  api_key  = DmCloud.config[:secret_key]

  #puts request
  normalized_request = normalize(request).to_s
  #puts 'identify:: normalized_values : ' + normalized_request + "\n" + '-' * 80

  params = user_id + normalized_request + api_key

  #puts 'identify:: Values before MD5 encrypt  : ' + params + "\n" + '-' * 80

  checksum = Digest::MD5.hexdigest(params)
  auth_token = user_id + ':' + checksum

  auth_token
end
normalize(params) click to toggle source

This block comes from Cloudkey gem. I discovered this gem far after I start this one and I will try to add file upload from http or ftp. (Missing in their gem)

# File lib/dm_cloud/signing.rb, line 178
def self.normalize params
      case params
      when Array
        params.collect { |element| normalize(element) }.join('')
      when Hash
        params.to_a.sort_by {|a,b| a.to_s }.collect {|array| array.first.to_s + normalize(array.last)}.join('')
      else
        params.to_s
      end
    end
security(type = nil) click to toggle source

The client must choose a security level for the signature. Security level defines the mechanism used by Dailymotion Cloud architecture to ensure the signed URL will be used by a single end-user. Params :

type :
  None: The signed URL will be valid for everyone
  ASNUM: The signed URL will only be valid for the AS of the end-user.
    The ASNUM (for Autonomous System Number) stands for the network identification,
    each ISP have a different ASNUM for instance.
  IP: The signed URL will only be valid for the IP of the end-user.
    This security level may wrongly block some users
    which have their internet access load-balanced between several proxies.
    This is the case in some office network or some ISPs.
  User-Agent: Used in addition to one of the two former levels,
    this level a limit on the exact user-agent of the end-user.
    This is more secure but in some specific condition may lead to wrongly blocked users.
  Use Once: The signed URL will only be usable once.
    Note: should not be used with stream URLs.
  Country: The URL can only be queried from specified countrie(s).
    The rule can be reversed to allow all countries except some.
  Referer: The URL can only be queried
    if the Referer HTTP header contains a specified value.
    If the URL contains a Referer header with a different value,
    the request is refused. If the Referer header is missing,
    the request is accepted in order to prevent from false positives as some browsers,
    anti-virus or enterprise proxies may remove this header.
  Delegate: This option instructs the signing algorithm
    that security level information won’t be embeded into the signature
    but gathered and lock at the first use.

Result :

Return a string which contain the signed url like
http://cdn.DmCloud.net/route/<user_id>/<media_id>/<asset_name>.<asset_extension>?auth=<auth_token>
# File lib/dm_cloud/signing.rb, line 113
def self.security(type = nil)
  type = :none unless type
  type = type.to_sym if type.class == String

  case type
    when :none
      0 # None
    when :delegate
      1 << 0  # None
    when :asnum
      1 << 1  # The number part of the end-user AS prefixed by the ‘AS’ string (ie: as=AS41690)
    when :ip
      1 << 2  # The end-user quad dotted IP address (ie: ip=195.8.215.138)
    when :user_agent
      1 << 3  # The end-user browser user-agent (parameter name is ua)
    when :use_once
      1 << 4  # None
    when :country
      1 << 5  # A list of 2 characters long country codes in lowercase by comas. If the list starts with a dash, the rule is inverted (ie: cc=fr,gb,de or cc=-fr,it). This data have to be stored in pub-sec-data component
    when :referer
      1 << 6  # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
    when :referer_strict
      1 << 15 # Same as Referer (will deny direct access as well)
  end
end
security_data(type, value = nil) click to toggle source
# File lib/dm_cloud/signing.rb, line 139
def self.security_data(type, value = nil)
  type = type.to_sym if type.class == String

  case type
    when :asnum
      "as=#{value}"  # The number part of the end-user AS prefixed by the ‘AS’ string (ie: as=AS41690)
    when :ip
      "ip=#{value}"  # The end-user quad dotted IP address (ie: ip=195.8.215.138)
    when :user_agent
      "ua=#{value}"  # The end-user browser user-agent (parameter name is ua)
    when :country
      "cc=#{value}"  # A list of 2 characters long country codes in lowercase by comas. If the list starts with a dash, the rule is inverted (ie: cc=fr,gb,de or cc=-fr,it). This data have to be stored in pub-sec-data component
    when :referer
      "rf=#{value}"  # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
    when :referer_strict
      "rf=#{value}"  # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
    else
      nil
  end
end
security_pub_sec_data(type, value) click to toggle source
# File lib/dm_cloud/signing.rb, line 160
def self.security_pub_sec_data(type, value)
  type = type.to_sym if type.class == String

  case type
    when :country
      "cc=#{value}"  # A list of 2 characters long country codes in lowercase by comas. If the list starts with a dash, the rule is inverted (ie: cc=fr,gb,de or cc=-fr,it). This data have to be stored in pub-sec-data component
    when :referer
      "rf=#{value}"  # A list of URL prefixes separated by spaces stored in the pub-sec-data component (ex: rf=http;//domain.com/a/+http:/domain.com/b/).
    else
      nil
  end
end
sign(stream, security_datas = nil) click to toggle source

To sign a URL, the client needs a secret shared with Dailymotion Cloud. This secret is call client secret and is available in the back-office interface. Params:

expires: An expiration timestamp.
sec-level: A security level mask.
url-no-query: The URL without the query-string.
nonce: A 8 characters-long random alphanumeric lowercase string to make the signature unique.
secret: The client secret.
sec-data: If sec-level doesn’t have the DELEGATED bit activated,
  this component contains concatenated informations
  for all activated sec levels.
pub-sec-data: Some sec level data have to be passed in clear in the signature.
  To generate this component the parameters are serialized using x-www-form-urlencoded, compressed with gzip and encoded in base64.

Result :

return a string which contain the signed url like
<expires>-<sec>-<nonce>-<md5sum>[-<pub-sec-data>]
# File lib/dm_cloud/signing.rb, line 46
def self.sign(stream, security_datas = nil)
  raise StandardError, "missing :stream in params" unless stream
  sec_level = security(DmCloud.config[:security_level])
  sec_data = security_data(DmCloud.config[:security_level], security_datas) unless security_datas.nil?

  base = {
    :sec_level => sec_level,
    :url_no_query => stream,
    :expires => (Time.now + 3.hours).to_i, # 3 hour from now
    :nonce => SecureRandom.hex(16)[0,16],
    :secret => DmCloud.config[:secret_key]
  }
  base.merge!(:sec_data => sec_data, :pub_sec_data => sec_data) unless sec_data.nil?

  digest_struct = build_digest_struct(base)
  check_sum = Digest::MD5.hexdigest(digest_struct)

  signed_url = [base[:expires], base[:sec_level], base[:nonce], check_sum].compact
  signed_url.merge!(:pub_sec_data => sec_data) unless sec_data.nil?

  # puts signed_url

  signed_url = signed_url.join('-')
  signed_url
end