require 'puppet/network/authorization'
require 'puppet/network/http/api/indirection_type'
class Puppet::Network::HTTP::API::IndirectedRoutes
include Puppet::Network::Authorization
# How we map http methods and the indirection name in the URI
# to an indirection method.
METHOD_MAP = {
"GET" => {
:plural => :search,
:singular => :find
},
"POST" => {
:singular => :find,
},
"PUT" => {
:singular => :save
},
"DELETE" => {
:singular => :destroy
},
"HEAD" => {
:singular => :head
}
}
IndirectionType = Puppet::Network::HTTP::API::IndirectionType
def self.routes
Puppet::Network::HTTP::Route.path(/.*/).any(new)
end
# handle an HTTP request
def call(request, response)
indirection, method, key, params = uri2indirection(request.method, request.path, request.params)
certificate = request.client_cert
if !indirection.allow_remote_requests?
# TODO: should we tell the user we found an indirection but it doesn't
# allow remote requests, or just pretend there's no handler at all? what
# are the security implications for the former?
raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("No handler for %{indirection}") % { indirection: indirection.name }, :NO_INDIRECTION_REMOTE_REQUESTS)
end
overrides = {
trusted_information: Puppet::Context::TrustedInformation.remote(params[:authenticated], params[:node], certificate),
}
if params[:environment]
overrides[:current_environment] = params[:environment]
end
Puppet.override(overrides) do
send("do_#{method}", indirection, key, params, request, response)
end
end
def uri2indirection(http_method, uri, params)
# the first field is always nil because of the leading slash,
indirection_type, version, indirection_name, key = uri.split("/", 5)[1..-1]
url_prefix = "/#{indirection_type}/#{version}"
environment = params.delete(:environment)
if indirection_name !~ /^\w+$/
raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(
_("The indirection name must be purely alphanumeric, not '%{indirection_name}'") % { indirection_name: indirection_name })
end
# this also depluralizes the indirection_name if it is a search
method = indirection_method(http_method, indirection_name)
# check whether this indirection matches the prefix and version in the
# request
if url_prefix != IndirectionType.url_prefix_for(indirection_name)
raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(
_("Indirection '%{indirection_name}' does not match url prefix '%{url_prefix}'") % { indirection_name: indirection_name, url_prefix: url_prefix })
end
indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym)
if !indirection
raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(
_("Could not find indirection '%{indirection_name}'") % { indirection_name: indirection_name },
Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND)
end
if !environment
raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(
_("An environment parameter must be specified"))
end
if ! Puppet::Node::Environment.valid_name?(environment)
raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(
_("The environment must be purely alphanumeric, not '%{environment}'") % { environment: environment })
end
configured_environment = Puppet.lookup(:environments).get(environment)
unless configured_environment.nil?
configured_environment = configured_environment.override_from_commandline(Puppet.settings)
params[:environment] = configured_environment
end
begin
check_authorization(method, "#{url_prefix}/#{indirection_name}/#{key}", params)
rescue Puppet::Network::AuthorizationError => e
raise Puppet::Network::HTTP::Error::HTTPNotAuthorizedError.new(e.message)
end
if configured_environment.nil? && indirection.terminus.require_environment?
raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(
_("Could not find environment '%{environment}'") % { environment: environment })
end
params.delete(:bucket_path)
if key == "" or key.nil?
raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(
_("No request key specified in %{uri}") % { uri: uri })
end
[indirection, method, key, params]
end
def self.request_to_uri(request)
uri, body = request_to_uri_and_body(request)
"#{uri}?#{body}"
end
def self.request_to_uri_and_body(request)
url_prefix = IndirectionType.url_prefix_for(request.indirection_name.to_s)
indirection = request.method == :search ? pluralize(request.indirection_name.to_s) : request.indirection_name.to_s
["#{url_prefix}/#{indirection}/#{Puppet::Util.uri_encode(request.key)}", "environment=#{request.environment.name}&#{request.query_string}"]
end
private
# Execute our find.
def do_find(indirection, key, params, request, response)
result = indirection.find(key, params)
unless result
raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find %{value0} %{key}") % { value0: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND)
end
rendered_result = result
rendered_format = first_response_formatter_for(indirection.model, request) do |format|
if result.respond_to?(:render)
Puppet::Util::Profiler.profile(_("Rendered result in %{format}") % { format: format }, [:http, :v3_render, format]) do
rendered_result = result.render(format)
end
end
end
Puppet::Util::Profiler.profile(_("Sent response"), [:http, :v3_response]) do
response.respond_with(200, rendered_format, rendered_result)
end
end
# Execute our head.
def do_head(indirection, key, params, request, response)
unless indirection.head(key, params)
raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find %{indirection} %{key}") % { indirection: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND)
end
# No need to set a response because no response is expected from a
# HEAD request. All we need to do is not die.
end
# Execute our search.
def do_search(indirection, key, params, request, response)
result = indirection.search(key, params)
if result.nil?
raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find instances in %{indirection} with '%{key}'") % { indirection: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND)
end
rendered_result = nil
rendered_format = first_response_formatter_for(indirection.model, request) do |format|
rendered_result = indirection.model.render_multiple(format, result)
end
response.respond_with(200, rendered_format, rendered_result)
end
# Execute our destroy.
def do_destroy(indirection, key, params, request, response)
formatter = accepted_response_formatter_or_json_for(indirection.model, request)
result = indirection.destroy(key, params)
response.respond_with(200, formatter, formatter.render(result))
end
# Execute our save.
def do_save(indirection, key, params, request, response)
formatter = accepted_response_formatter_or_json_for(indirection.model, request)
sent_object = read_body_into_model(indirection.model, request)
result = indirection.save(sent_object, key)
response.respond_with(200, formatter, formatter.render(result))
end
# Return the first response formatter that didn't cause the yielded
# block to raise a FormatError.
def first_response_formatter_for(model, request, &block)
formats = accepted_response_formatters_for(model, request)
formatter = formats.find do |format|
begin
yield format
true
rescue Puppet::Network::FormatHandler::FormatError => err
Puppet.log_exception(err, err.message, level: :debug)
false
end
end
return formatter if formatter
raise Puppet::Network::HTTP::Error::HTTPNotAcceptableError.new(
_("No supported formats are acceptable (Accept: %{accepted_formats})") % { accepted_formats: formats.map(&:mime).join(', ') },
Puppet::Network::HTTP::Issues::UNSUPPORTED_FORMAT)
end
# Return an array of response formatters that the client accepts and
# the server supports.
def accepted_response_formatters_for(model_class, request)
request.response_formatters_for(model_class.supported_formats)
end
# Return the first response formatter that the client accepts and
# the server supports, or default to 'application/json'.
def accepted_response_formatter_or_json_for(model_class, request)
request.response_formatters_for(model_class.supported_formats, "application/json").first
end
def read_body_into_model(model_class, request)
data = request.body.to_s
formatter = request.formatter
if formatter.supported?(model_class)
begin
return model_class.convert_from(formatter.name.to_s, data)
rescue => e
raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(
_("The request body is invalid: %{message}") % { message: e.message })
end
end
#TRANSLATORS "mime-type" is a keyword and should not be translated
raise Puppet::Network::HTTP::Error::HTTPUnsupportedMediaTypeError.new(
_("Client sent a mime-type (%{header}) that doesn't correspond to a format we support") % { header: request.headers['content-type'] },
Puppet::Network::HTTP::Issues::UNSUPPORTED_MEDIA_TYPE)
end
def indirection_method(http_method, indirection)
raise Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError.new(
_("No support for http method %{http_method}") % { http_method: http_method }) unless METHOD_MAP[http_method]
method = METHOD_MAP[http_method][plurality(indirection)]
unless method
raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(
_("No support for plurality %{indirection} for %{http_method} operations") % { indirection: plurality(indirection), http_method: http_method })
end
method
end
def self.pluralize(indirection)
return(indirection == "status" ? "statuses" : indirection + "s")
end
private_class_method :pluralize
def plurality(indirection)
# NOTE These specific hooks for paths are ridiculous, but it's a *many*-line
# fix to not need this, and our goal is to move away from the complication
# that leads to the fix being too long.
return :singular if indirection == "facts"
return :singular if indirection == "status"
return :singular if indirection == "certificate_status"
result = (indirection =~ /s$|_search$/) ? :plural : :singular
indirection.sub!(/s$|_search$/, '')
indirection.sub!(/statuse$/, 'status')
result
end
end
Anons79 File Manager Version 1.0, Coded By Anons79
Email: [email protected]