summaryrefslogtreecommitdiff
path: root/lib/chef_zero/endpoints
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef_zero/endpoints')
-rw-r--r--lib/chef_zero/endpoints/actor_endpoint.rb113
-rw-r--r--lib/chef_zero/endpoints/actor_key_endpoint.rb97
-rw-r--r--lib/chef_zero/endpoints/actor_keys_endpoint.rb126
-rw-r--r--lib/chef_zero/endpoints/rest_object_endpoint.rb24
-rw-r--r--lib/chef_zero/endpoints/server_api_version_endpoint.rb2
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