module Roda::RodaPlugins::HmacPaths::InstanceMethods

def hmac_path(path, opts=OPTS)

:until :: Make the given path valid until the given Time.
:seconds :: Make the given path valid for the given integer number of seconds.
the top level of the routing tree.
to the empty string, which will returns paths valid for r.hmac_path at
the already matched path of the routing tree using r.hmac_path. Defaults
:root :: Should be an empty string or string starting with +/+. This will be
limits the returned path to only be valid for that exact query string.
:params :: Includes parameters in the query string of the returned path, and
namespace when there is a default namespace, pass a nil value.
provided, the default namespace is used. To explicitly not use a
:namespace :: Make the HMAC value depend on the given namespace. If this is not
:method :: Limits the returned path to only be valid for the given request method.

valid paths. The given path should be a string starting with +/+. Options:
(directly or indirectly). This can prevent users of a site from enumerating
users can only request paths that they have been provided by the application
Return a path with an HMAC. Designed to be used with r.hmac_path, to make sure
def hmac_path(path, opts=OPTS)
  unless path.is_a?(String) && path.getbyte(0) == 47
    raise RodaError, "path must be a string starting with /"
  end
  root = opts[:root] || ''
  unless root.is_a?(String) && ((root_byte = root.getbyte(0)) == 47 || root_byte == nil)
    raise RodaError, "root must be empty string or string starting with /"
  end
  if valid_until = opts[:until]
    valid_until = valid_until.to_i
  elsif seconds = opts[:seconds]
    valid_until = Time.now.to_i + seconds
  end
  flags = String.new
  path = path.dup
  if method = opts[:method]
    flags << 'm'
  end
  if params = opts[:params]
    flags << 'p'
    path << '?' << Rack::Utils.build_query(params)
  end
  if hmac_path_namespace(opts)
    flags << 'n'
  end
  if valid_until
    flags << 't'
    path = "/#{valid_until}#{path}"
  end
  flags << '0' if flags.empty?
  
  hmac_path = if method
    "#{method.to_s.upcase}:/#{flags}#{path}"
  else
    "/#{flags}#{path}"
  end
  "#{root}/#{hmac_path_hmac(root, hmac_path, opts)}/#{flags}#{path}"
end

def hmac_path_default_namespace

The default namespace to use for hmac_path, if a :namespace option is not provided.
def hmac_path_default_namespace
  if (key = opts[:hmac_paths_namespace_session_key]) && (value = session[key])
    value.to_s
  end
end

def hmac_path_hmac(root, path, opts=OPTS)

The HMAC to use in hmac_path, for the given root, path, and options.
def hmac_path_hmac(root, path, opts=OPTS)
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_path_hmac_secret(root, opts), path)
end

def hmac_path_hmac_secret(root, opts=OPTS)

This always returns a hexidecimal string.
using the secret given in the plugin, for the given root and options.
The secret used to calculate the HMAC in hmac_path. This is itself an HMAC, created
def hmac_path_hmac_secret(root, opts=OPTS)
  secret = opts[:secret] || self.opts[:hmac_paths_secret]
  if namespace = hmac_path_namespace(opts)
    secret = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, namespace)
  end
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, secret, root)
end

def hmac_path_namespace(opts=OPTS)

use the value of the related session key, if present.
provided, and a :namespace_session_key option was provided, this will
The namespace to use for the hmac path. If a :namespace option is not
def hmac_path_namespace(opts=OPTS)
  opts.fetch(:namespace){hmac_path_default_namespace}
end