diff options
Diffstat (limited to 'lib/chef_zero/endpoints')
-rw-r--r-- | lib/chef_zero/endpoints/actor_endpoint.rb | 113 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/actor_key_endpoint.rb | 97 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/actor_keys_endpoint.rb | 126 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/rest_object_endpoint.rb | 24 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/server_api_version_endpoint.rb | 2 |
5 files changed, 339 insertions, 23 deletions
diff --git a/lib/chef_zero/endpoints/actor_endpoint.rb b/lib/chef_zero/endpoints/actor_endpoint.rb index 1572ac1..12fbe11 100644 --- a/lib/chef_zero/endpoints/actor_endpoint.rb +++ b/lib/chef_zero/endpoints/actor_endpoint.rb @@ -10,6 +10,7 @@ module ChefZero class ActorEndpoint < RestObjectEndpoint def delete(request) result = super + if request.rest_path[0] == 'users' list_data(request, [ 'organizations' ]).each do |org| begin @@ -18,12 +19,15 @@ module ChefZero end end end + + delete_actor_keys!(request) result end def put(request) # Find out if we're updating the public key. request_body = FFI_Yajl::Parser.parse(request.body, :create_additions => false) + if request_body['public_key'].nil? # If public_key is null, then don't overwrite it. Weird patchiness. body_modified = true @@ -33,17 +37,17 @@ module ChefZero end # Generate private_key if requested. - if request_body.has_key?('private_key') + if request_body.key?('private_key') body_modified = true - if request_body['private_key'] + + if request_body.delete('private_key') private_key, public_key = server.gen_key_pair updating_public_key = true request_body['public_key'] = public_key end - request_body.delete('private_key') end - # Save request + # Put modified body back in `request.body` request.body = FFI_Yajl::Encoder.encode(request_body, :pretty => true) if body_modified # PUT /clients is patchy @@ -53,27 +57,29 @@ module ChefZero # Inject private_key into response, delete public_key/password if applicable if result[0] == 200 || result[0] == 201 + client_or_user_name = identity_key_value(request) || request.rest_path[-1] + + if is_rename?(request) + rename_keys!(request, client_or_user_name) + end + if request.rest_path[0] == 'users' - key = nil - identity_keys.each do |identity_key| - key ||= request_body[identity_key] - end - key ||= request.rest_path[-1] response = { - 'uri' => build_uri(request.base_uri, [ 'users', key ]) + 'uri' => build_uri(request.base_uri, [ 'users', client_or_user_name ]) } else response = FFI_Yajl::Parser.parse(result[2], :create_additions => false) end - if request.rest_path[2] == 'clients' + if client?(request) response['private_key'] = private_key ? private_key : false else response['private_key'] = private_key if private_key + response.delete('public_key') unless updating_public_key end - response.delete('public_key') if !updating_public_key && request.rest_path[2] == 'users' response.delete('password') + json_response(result[0], response) else result @@ -81,13 +87,86 @@ module ChefZero end def populate_defaults(request, response_json) - response = FFI_Yajl::Parser.parse(response_json, :create_additions => false) - if request.rest_path[2] == 'clients' - response = ChefData::DataNormalizer.normalize_client(response,request.rest_path[3], request.rest_path[1]) + response = FFI_Yajl::Parser.parse(response_json, create_additions: false) + + populated_response = + if client?(request) + ChefData::DataNormalizer.normalize_client( + response, + response["name"] || request.rest_path[-1], + request.rest_path[1] + ) + else + ChefData::DataNormalizer.normalize_user( + response, + response["username"] || request.rest_path[-1], + identity_keys, + server.options[:osc_compat], + request.method + ) + end + + FFI_Yajl::Encoder.encode(populated_response, pretty: true) + end + + private + + # Move key data to new path + def rename_keys!(request, new_client_or_user_name) + orig_keys_path = keys_path_base(request) + new_keys_path = orig_keys_path.dup + .tap {|path| path[-2] = new_client_or_user_name } + + key_names = list_data_or_else(request, orig_keys_path, nil) + return unless key_names # No keys to move + + key_names.each do |key_name| + # Get old data + orig_path = [ *orig_keys_path, key_name ] + data = get_data(request, orig_path, :data_store_exceptions) + + # Copy data to new path + create_data( + request, + new_keys_path, key_name, + data, + :create_dir + ) + end + + # Delete original data + delete_data_dir(request, orig_keys_path, :recursive, :data_store_exceptions) + end + + def delete_actor_keys!(request) + path = keys_path_base(request)[0..-2] + delete_data_dir(request, path, :recursive, :data_store_exceptions) + rescue DataStore::DataNotFoundError + end + + def client?(request, rest_path=nil) + rest_path ||= request.rest_path + request.rest_path[2] == "clients" + end + + # Return the data store keys path for the request client or user, e.g. + # + # [ "organizations", <org>, "client_keys", <client>, "keys" ] + # + # Or: + # + # [ "user_keys", <user>, "keys" ] + # + def keys_path_base(request, client_or_user_name=nil) + rest_path = (rest_path || request.rest_path).dup + rest_path[-1] = client_or_user_name if client_or_user_name + + if client?(request, rest_path) + [ *rest_path[0..1], "client_keys" ] else - response = ChefData::DataNormalizer.normalize_user(response, request.rest_path[3], identity_keys, server.options[:osc_compat], request.method) + [ "user_keys" ] end - FFI_Yajl::Encoder.encode(response, :pretty => true) + .push(rest_path.last, "keys") end end end diff --git a/lib/chef_zero/endpoints/actor_key_endpoint.rb b/lib/chef_zero/endpoints/actor_key_endpoint.rb new file mode 100644 index 0000000..d45570d --- /dev/null +++ b/lib/chef_zero/endpoints/actor_key_endpoint.rb @@ -0,0 +1,97 @@ +require 'ffi_yajl' +require 'chef_zero/rest_base' + +module ChefZero + module Endpoints + # /users/USER/keys/NAME + # /organizations/ORG/clients/CLIENT/keys/NAME + class ActorKeyEndpoint < RestBase + DEFAULT_PUBLIC_KEY_NAME = "default".freeze + + def get(request) + # Try to get the actor so a 404 is returned if it doesn't exist + actor_json = get_actor_json(request) + + if request.rest_path[-1] == DEFAULT_PUBLIC_KEY_NAME + actor_data = FFI_Yajl::Parser.parse(actor_json, create_additions: false) + default_public_key = default_public_key_from_actor(actor_data) + return json_response(200, default_public_key) + end + + key_path = data_path(request) + already_json_response(200, get_data(request, key_path)) + end + + def delete(request) + # Try to get the actor so a 404 is returned if it doesn't exist + actor_json = get_actor_json(request) + + if request.rest_path[-1] == DEFAULT_PUBLIC_KEY_NAME + actor_data = FFI_Yajl::Parser.parse(actor_json, create_additions: false) + default_public_key = delete_actor_default_public_key!(request, actor_data) + return json_response(200, default_public_key) + end + + key_path = data_path(request) + + data = get_data(request, key_path) + delete_data(request, key_path) + + already_json_response(200, data) + end + + def put(request) + # We grab the old data to trigger a 404 if it doesn't exist + get_data(request, data_path(request)) + + set_data(request, path, request.body) + end + + private + + # Returns the keys data store path, which is the same as + # `request.rest_path` except with "user_keys" instead of "users" or + # "client_keys" instead of "clients." + def data_path(request) + request.rest_path.dup.tap do |path| + if client?(request) + path[2] = "client_keys" + else + path[0] = "user_keys" + end + end + end + + def default_public_key_from_actor(actor_data) + { "name" => DEFAULT_PUBLIC_KEY_NAME, + "public_key" => actor_data["public_key"], + "expiration_date" => "infinity" } + end + + def delete_actor_default_public_key!(request, actor_data) + new_actor_data = actor_data.merge("public_key" => nil) + + set_data( + request, + actor_path(request), + FFI_Yajl::Encoder.encode(new_actor_data, pretty: true) + ) + + default_public_key_from_actor(actor_data) + end + + def get_actor_json(request) + get_data(request, actor_path(request)) + end + + def client?(request) + request.rest_path[2] == "clients" + end + + def actor_path(request) + return request.rest_path[0..3] if client?(request) + request.rest_path[0..1] + end + end + end +end diff --git a/lib/chef_zero/endpoints/actor_keys_endpoint.rb b/lib/chef_zero/endpoints/actor_keys_endpoint.rb new file mode 100644 index 0000000..8bf3981 --- /dev/null +++ b/lib/chef_zero/endpoints/actor_keys_endpoint.rb @@ -0,0 +1,126 @@ +require 'ffi_yajl' +require 'chef_zero/rest_base' + +module ChefZero + module Endpoints + # /users/USER/keys + # /organizations/ORG/clients/CLIENT/keys + class ActorKeysEndpoint < RestBase + DEFAULT_PUBLIC_KEY_NAME = "default" + DATE_FORMAT = "%FT%TZ" # e.g. 2015-12-24T21:00:00Z + + def get(request) + path = data_path(request) + + # Get actor or 404 if it doesn't exist + actor_path = request.rest_path[ client?(request) ? 0..3 : 0..1 ] + actor_json = get_data(request, actor_path) + + key_names = list_data_or_else(request, path, []) + key_names.unshift(DEFAULT_PUBLIC_KEY_NAME) if actor_has_default_public_key?(actor_json) + + result = key_names.map do |key_name| + list_key(request, [ *path, key_name ]) + end + + json_response(200, result) + end + + def post(request) + request_body = FFI_Yajl::Parser.parse(request.body, create_additions: false) + + # Try loading the client or user so a 404 is returned if it doesn't exist + actor_path = request.rest_path[ client?(request) ? 0..3 : 0..1 ] + actor_json = get_data(request, actor_path) + + generate_keys = request_body["public_key"].nil? + + if generate_keys + private_key, public_key = server.gen_key_pair + else + public_key = request_body['public_key'] + end + + key_name = request_body["name"] + + if key_name == DEFAULT_PUBLIC_KEY_NAME + store_actor_default_public_key!(request, actor_path, actor_json, public_key) + else + store_actor_public_key!(request, key_name, public_key, request_body["expiration_date"]) + end + + response_body = { "uri" => key_uri(request, key_name) } + response_body["private_key"] = private_key if generate_keys + + json_response(201, response_body, + headers: { "Location" => response_body["uri"] }) + end + + private + + def store_actor_public_key!(request, name, public_key, expiration_date) + data = FFI_Yajl::Encoder.encode( + "name" => name, + "public_key" => public_key, + "expiration_date" => expiration_date + ) + + create_data(request, data_path(request), name, data, :create_dir) + end + + def store_actor_default_public_key!(request, actor_path, actor_json, public_key) + actor_data = FFI_Yajl::Parser.parse(actor_json, create_additions: false) + + if actor_data["public_key"] + raise RestErrorResponse.new(409, "Object already exists: #{key_uri(request, DEFAULT_PUBLIC_KEY_NAME)}") + end + + actor_data["public_key"] = public_key + set_data(request, actor_path, FFI_Yajl::Encoder.encode(actor_data, pretty: true)) + end + + # Returns the keys data store path, which is the same as + # `request.rest_path` except with "user_keys" instead of "users" or + # "client_keys" instead of "clients." + def data_path(request) + request.rest_path.dup.tap do |path| + if client?(request) + path[2] = "client_keys" + else + path[0] = "user_keys" + end + end + end + + def list_key(request, data_path) + key_name, expiration_date = + if data_path[-1] == DEFAULT_PUBLIC_KEY_NAME + [ DEFAULT_PUBLIC_KEY_NAME, "infinity" ] + else + FFI_Yajl::Parser.parse(get_data(request, data_path), create_additions: false) + .values_at("name", "expiration_date") + end + + expired = expiration_date != "infinity" && + DateTime.now > DateTime.strptime(expiration_date, DATE_FORMAT) + + { "name" => key_name, + "uri" => key_uri(request, key_name), + "expired" => expired } + end + + def client?(request) + request.rest_path[2] == "clients" + end + + def key_uri(request, key_name) + build_uri(request.base_uri, [ *request.rest_path, key_name ]) + end + + def actor_has_default_public_key?(actor_json) + actor_data = FFI_Yajl::Parser.parse(actor_json, create_additions: false) + !!actor_data["public_key"] + end + end + end +end diff --git a/lib/chef_zero/endpoints/rest_object_endpoint.rb b/lib/chef_zero/endpoints/rest_object_endpoint.rb index 9e978b4..95a3122 100644 --- a/lib/chef_zero/endpoints/rest_object_endpoint.rb +++ b/lib/chef_zero/endpoints/rest_object_endpoint.rb @@ -21,12 +21,11 @@ module ChefZero def put(request) # We grab the old body to trigger a 404 if it doesn't exist old_body = get_data(request) - request_json = FFI_Yajl::Parser.parse(request.body, :create_additions => false) - key = identity_keys.map { |k| request_json[k] }.select { |v| v }.first - key ||= request.rest_path[-1] + # If it's a rename, check for conflict and delete the old value - rename = key != request.rest_path[-1] - if rename + if is_rename?(request) + key = identity_key_value(request) + begin create_data(request, request.rest_path[0..-2], key, request.body, :data_store_exceptions) rescue DataStore::DataAlreadyExistsError @@ -56,8 +55,23 @@ module ChefZero return FFI_Yajl::Encoder.encode(merged_json, :pretty => true) end end + request.body end + + private + + # Get the value of the (first existing) identity key from the request body or nil + def identity_key_value(request) + request_json = FFI_Yajl::Parser.parse(request.body, :create_additions => false) + identity_keys.map { |k| request_json[k] }.compact.first + end + + # Does this request change the value of the identity key? + def is_rename?(request) + return false unless key = identity_key_value(request) + key != request.rest_path[-1] + end end end end diff --git a/lib/chef_zero/endpoints/server_api_version_endpoint.rb b/lib/chef_zero/endpoints/server_api_version_endpoint.rb index 631f105..8ddeaba 100644 --- a/lib/chef_zero/endpoints/server_api_version_endpoint.rb +++ b/lib/chef_zero/endpoints/server_api_version_endpoint.rb @@ -7,7 +7,7 @@ module ChefZero API_VERSION = 1 def get(request) json_response(200, {"min_api_version"=>MIN_API_VERSION, "max_api_version"=>MAX_API_VERSION}, - request.api_version, API_VERSION) + request_version: request.api_version, response_version: API_VERSION) end end end |