summaryrefslogtreecommitdiff
path: root/spec/unit/key_spec.rb
diff options
context:
space:
mode:
authortylercloke <tylercloke@gmail.com>2015-04-07 17:31:34 -0700
committertylercloke <tylercloke@gmail.com>2015-04-20 15:19:06 -0700
commit855954929adf26aecaad8cd0f549f03353825d6c (patch)
treef4da617c866b40e6102cfbe3d2ac936e80802cba /spec/unit/key_spec.rb
parentde7dc03e8da3c30f9b833b25e58f4e087f978c1a (diff)
downloadchef-855954929adf26aecaad8cd0f549f03353825d6c.tar.gz
Implemented the Key object corrosponding to key rotation API endpoint.tc/chef_key_object
Diffstat (limited to 'spec/unit/key_spec.rb')
-rw-r--r--spec/unit/key_spec.rb577
1 files changed, 577 insertions, 0 deletions
diff --git a/spec/unit/key_spec.rb b/spec/unit/key_spec.rb
new file mode 100644
index 0000000000..9f37b8aa12
--- /dev/null
+++ b/spec/unit/key_spec.rb
@@ -0,0 +1,577 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'chef/key'
+
+describe Chef::Key do
+ # whether user or client irrelevent to these tests
+ let(:key) { Chef::Key.new("original_actor", "user") }
+ let(:public_key_string) do
+ <<EOS
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----
+EOS
+ end
+
+ shared_examples_for "fields with username type validation" do
+ context "when invalid input is passed" do
+ # It is not feasible to check all invalid characters. Here are a few
+ # that we probably care about.
+ it "should raise an ArgumentError" do
+ # capital letters
+ expect { key.send(field, "Bar") }.to raise_error(ArgumentError)
+ # slashes
+ expect { key.send(field, "foo/bar") }.to raise_error(ArgumentError)
+ # ?
+ expect { key.send(field, "foo?") }.to raise_error(ArgumentError)
+ # &
+ expect { key.send(field, "foo&") }.to raise_error(ArgumentError)
+ # spaces
+ expect { key.send(field, "foo ") }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ shared_examples_for "string fields that are settable" do
+ context "when it is set with valid input" do
+ it "should set the field" do
+ key.send(field, valid_input)
+ expect(key.send(field)).to eq(valid_input)
+ end
+ end
+
+ context "when you feed it anything but a string" do
+ it "should raise an ArgumentError" do
+ expect { key.send(field, Hash.new) }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+
+ describe "when a new Chef::Key object is initialized with invalid input" do
+ it "should raise an InvalidKeyArgument" do
+ expect { Chef::Key.new("original_actor", "not_a_user_or_client") }.to raise_error(Chef::Exceptions::InvalidKeyArgument)
+ end
+ end
+
+ describe "when a new Chef::Key object is initialized with valid input" do
+ it "should be a Chef::Key" do
+ expect(key).to be_a_kind_of(Chef::Key)
+ end
+
+ it "should properly set the actor" do
+ expect(key.actor).to eq("original_actor")
+ end
+ end
+
+ describe "when actor field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :actor }
+ let(:valid_input) { "new_field_value" }
+ end
+
+ it_should_behave_like "fields with username type validation" do
+ let(:field) { :actor }
+ end
+ end
+
+ describe "when the name field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :name }
+ let(:valid_input) { "new_field_value" }
+ end
+ end
+
+ describe "when the private_key field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :private_key }
+ let(:valid_input) { "new_field_value" }
+ end
+ end
+
+ describe "when the public_key field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :public_key }
+ let(:valid_input) { "new_field_value" }
+ end
+
+ context "when create_key is true" do
+ before do
+ key.create_key true
+ end
+
+ it "should raise an InvalidKeyAttribute" do
+ expect { key.public_key public_key_string }.to raise_error(Chef::Exceptions::InvalidKeyAttribute)
+ end
+ end
+ end
+
+ describe "when the create_key field is set" do
+ context "when it is set to true" do
+ it "should set the field" do
+ key.create_key(true)
+ expect(key.create_key).to eq(true)
+ end
+ end
+
+ context "when it is set to false" do
+ it "should set the field" do
+ key.create_key(false)
+ expect(key.create_key).to eq(false)
+ end
+ end
+
+ context "when anything but a TrueClass or FalseClass is passed" do
+ it "should raise an ArgumentError" do
+ expect { key.create_key "not_a_boolean" }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "when public_key is defined" do
+ before do
+ key.public_key public_key_string
+ end
+
+ it "should raise an InvalidKeyAttribute" do
+ expect { key.create_key true }.to raise_error(Chef::Exceptions::InvalidKeyAttribute)
+ end
+ end
+ end
+
+ describe "when the expiration_date field is set" do
+ context "when a valid date is passed" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :public_key }
+ let(:valid_input) { "2020-12-24T21:00:00Z" }
+ end
+ end
+
+ context "when infinity is passed" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :public_key }
+ let(:valid_input) { "infinity" }
+ end
+ end
+
+ context "when an invalid date is passed" do
+ it "should raise an ArgumentError" do
+ expect { key.expiration_date "invalid_date" }.to raise_error(ArgumentError)
+ # wrong years
+ expect { key.expiration_date "20-12-24T21:00:00Z" }.to raise_error(ArgumentError)
+ end
+
+ context "when it is a valid UTC date missing a Z" do
+ it "should raise an ArgumentError" do
+ expect { key.expiration_date "2020-12-24T21:00:00" }.to raise_error(ArgumentError)
+ end
+ end
+ end
+ end # when the expiration_date field is set
+
+ describe "when serializing to JSON" do
+ shared_examples_for "common json operations" do
+ it "should serializes as a JSON object" do
+ expect(json).to match(/^\{.+\}$/)
+ end
+
+ it "should include the actor value under the key relative to the actor_field_name passed" do
+ expect(json).to include(%Q("#{new_key.actor_field_name}":"original_actor"))
+ end
+
+ it "should include the name field when present" do
+ new_key.name("monkeypants")
+ expect(new_key.to_json).to include(%q{"name":"monkeypants"})
+ end
+
+ it "should not include the name if not present" do
+ expect(json).to_not include("name")
+ end
+
+ it "should include the public_key field when present" do
+ new_key.public_key "this_public_key"
+ expect(new_key.to_json).to include(%q("public_key":"this_public_key"))
+ end
+
+ it "should not include the public_key if not present" do
+ expect(json).to_not include("public_key")
+ end
+
+ it "should include the private_key field when present" do
+ new_key.private_key "this_public_key"
+ expect(new_key.to_json).to include(%q("private_key":"this_public_key"))
+ end
+
+ it "should not include the private_key if not present" do
+ expect(json).to_not include("private_key")
+ end
+
+ it "should include the expiration_date field when present" do
+ new_key.expiration_date "2020-12-24T21:00:00Z"
+ expect(new_key.to_json).to include(%Q("expiration_date":"2020-12-24T21:00:00Z"))
+ end
+
+ it "should not include the expiration_date if not present" do
+ expect(json).to_not include("expiration_date")
+ end
+
+ it "should include the create_key field when present" do
+ new_key.create_key true
+ expect(new_key.to_json).to include(%q("create_key":true))
+ end
+
+ it "should not include the create_key if not present" do
+ expect(json).to_not include("create_key")
+ end
+ end
+
+ context "when key is for a user" do
+ it_should_behave_like "common json operations" do
+ let(:new_key) { Chef::Key.new("original_actor", "user") }
+ let(:json) do
+ new_key.to_json
+ end
+ end
+ end
+
+ context "when key is for a client" do
+ it_should_behave_like "common json operations" do
+ let(:new_key) { Chef::Key.new("original_actor", "client") }
+ let(:json) do
+ new_key.to_json
+ end
+ end
+ end
+
+ end # when serializing to JSON
+
+ describe "when deserializing from JSON" do
+ shared_examples_for "a deserializable object" do
+ it "deserializes to a Chef::Key object" do
+ expect(key).to be_a_kind_of(Chef::Key)
+ end
+
+ it "preserves the actor" do
+ expect(key.actor).to eq("turtle")
+ end
+
+ it "preserves the name" do
+ expect(key.name).to eq("key_name")
+ end
+
+ it "includes the public key if present" do
+ expect(key.public_key).to eq(public_key_string)
+ end
+
+ it "includes the expiration_date if present" do
+ expect(key.expiration_date).to eq("infinity")
+ end
+
+ it "includes the private_key if present" do
+ expect(key.private_key).to eq("some_private_key")
+ end
+
+ it "includes the create_key if present" do
+ expect(key_with_create_key_field.create_key).to eq(true)
+ end
+ end
+
+ context "when deserializing a key for a user" do
+ it_should_behave_like "a deserializable object" do
+ let(:key) do
+ o = { "user" => "turtle",
+ "name" => "key_name",
+ "public_key" => public_key_string,
+ "private_key" => "some_private_key",
+ "expiration_date" => "infinity"}
+ Chef::Key.from_json(o.to_json)
+ end
+ let(:key_with_create_key_field) do
+ o = { "user" => "turtle",
+ "create_key" => true }
+ Chef::Key.from_json(o.to_json)
+ end
+ end
+ end
+
+ context "when deserializing a key for a client" do
+ it_should_behave_like "a deserializable object" do
+ let(:key) do
+ o = { "client" => "turtle",
+ "name" => "key_name",
+ "public_key" => public_key_string,
+ "private_key" => "some_private_key",
+ "expiration_date" => "infinity"}
+ Chef::Key.from_json(o.to_json)
+ end
+ let(:key_with_create_key_field) do
+ o = { "client" => "turtle",
+ "create_key" => true }
+ Chef::Key.from_json(o.to_json)
+ end
+ end
+ end
+ end # when deserializing from JSON
+
+
+ describe "API Interactions" do
+ let(:rest) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ Chef::Config[:chef_server_url] = "http://www.example.com/organizations/test_org"
+ r = double('rest')
+ allow(Chef::REST).to receive(:new).and_return(r)
+ r
+ end
+
+ let(:user_key) do
+ o = Chef::Key.new("foobar", "user")
+ o
+ end
+
+ let(:client_key) do
+ o = Chef::Key.new("foobar", "client")
+ o
+ end
+
+ describe "list" do
+ context "when listing keys for a user" do
+ let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] }
+ let(:inflated_response) { {"foobar" => user_key} }
+
+ it "lists all keys" do
+ expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_user("foobar")).to eq(response)
+ end
+
+ it "inflate all keys" do
+ allow(Chef::Key).to receive(:load_by_user).with(user_key.actor, "foobar").and_return(user_key)
+ expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_user("foobar", true)).to eq(inflated_response)
+ end
+
+ end
+
+ context "when listing keys for a client" do
+ let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] }
+ let(:inflated_response) { {"foobar" => client_key} }
+
+ it "lists all keys" do
+ expect(rest).to receive(:get_rest).with("clients/#{client_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_client("foobar")).to eq(response)
+ end
+
+ it "inflate all keys" do
+ allow(Chef::Key).to receive(:load_by_client).with(client_key.actor, "foobar").and_return(client_key)
+ expect(rest).to receive(:get_rest).with("clients/#{user_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_client("foobar", true)).to eq(inflated_response)
+ end
+
+ end
+ end
+
+
+ describe "create" do
+ shared_examples_for "create key" do
+ context "when a field is missing" do
+ it "should raise a MissingKeyAttribute" do
+ expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+
+ context "when the name field is missing" do
+ before do
+ key.public_key public_key_string
+ key.expiration_date "2020-12-24T21:00:00Z"
+ end
+
+ it "creates a new key via the API with the fingerprint as the name" do
+ expect(rest).to receive(:post_rest).with(url,
+ {"name" => "12:3e:33:73:0b:f4:ec:72:dc:f0:4c:51:62:27:08:76:96:24:f4:4a",
+ "public_key" => key.public_key,
+ "expiration_date" => key.expiration_date}).and_return({})
+ key.create
+ end
+ end
+
+ context "when every field is populated" do
+ before do
+ key.name "key_name"
+ key.public_key public_key_string
+ key.expiration_date "2020-12-24T21:00:00Z"
+ key.create_key false
+ end
+
+ context "when create_key is false" do
+ it "creates a new key via the API" do
+ expect(rest).to receive(:post_rest).with(url,
+ {"name" => key.name,
+ "public_key" => key.public_key,
+ "expiration_date" => key.expiration_date}).and_return({})
+ key.create
+ end
+ end
+
+ context "when create_key is true and public_key is nil" do
+ before do
+ key.delete_public_key
+ key.create_key true
+ end
+ it "should create a new key via the API" do
+ expect(rest).to receive(:post_rest).with(url,
+ {"name" => key.name,
+ "create_key" => true,
+ "expiration_date" => key.expiration_date}).and_return({})
+ key.create
+ end
+ end
+
+ context "when create_key is false and public_key is nil" do
+ before do
+ key.delete_public_key
+ key.create_key false
+ end
+ it "should raise an InvalidKeyArgument" do
+ expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+ end
+ end
+
+ context "when creating a user key" do
+ it_should_behave_like "create key" do
+ let(:url) { "users/#{key.actor}/keys" }
+ let(:key) { user_key }
+ end
+ end
+
+ context "when creating a client key" do
+ it_should_behave_like "create key" do
+ let(:url) { "clients/#{client_key.actor}/keys" }
+ let(:key) { client_key }
+ end
+ end
+ end # create
+
+ describe "update" do
+ shared_examples_for "update key" do
+ context "when name is missing" do
+ it "should raise an MissingKeyAttribute" do
+ expect { key.update }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+
+ context "when some fields are populated" do
+ before do
+ key.name "key_name"
+ key.expiration_date "2020-12-24T21:00:00Z"
+ end
+
+ it "should update the key via the API" do
+ expect(rest).to receive(:put_rest).with(url, key.to_hash).and_return({})
+ key.update
+ end
+ end
+ end
+
+ context "when creating a user key" do
+ it_should_behave_like "update key" do
+ let(:url) { "users/#{key.actor}/keys/#{key.name}" }
+ let(:key) { user_key }
+ end
+ end
+
+ context "when creating a client key" do
+ it_should_behave_like "update key" do
+ let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" }
+ let(:key) { client_key }
+ end
+ end
+
+ end #update
+
+ describe "load" do
+ shared_examples_for "load" do
+ it "should load a named key from the API" do
+ expect(rest).to receive(:get_rest).with(url).and_return({"user" => "foobar", "name" => "test_key_name", "public_key" => public_key_string, "expiration_date" => "infinity"})
+ key = Chef::Key.send(load_method, "foobar", "test_key_name")
+ expect(key.actor).to eq("foobar")
+ expect(key.name).to eq("test_key_name")
+ expect(key.public_key).to eq(public_key_string)
+ expect(key.expiration_date).to eq("infinity")
+ end
+ end
+
+ describe "load_by_user" do
+ it_should_behave_like "load" do
+ let(:load_method) { :load_by_user }
+ let(:url) { "users/foobar/keys/test_key_name" }
+ end
+ end
+
+ describe "load_by_client" do
+ it_should_behave_like "load" do
+ let(:load_method) { :load_by_client }
+ let(:url) { "clients/foobar/keys/test_key_name" }
+ end
+ end
+
+ end #load
+
+ describe "destroy" do
+ shared_examples_for "destroy key" do
+ context "when name is missing" do
+ it "should raise an MissingKeyAttribute" do
+ expect { Chef::Key.new("username", "user").destroy }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+
+ before do
+ key.name "key_name"
+ end
+ context "when name is not missing" do
+ it "should delete the key via the API" do
+ expect(rest).to receive(:delete_rest).with(url).and_return({})
+ key.destroy
+ end
+ end
+ end
+
+ context "when destroying a user key" do
+ it_should_behave_like "destroy key" do
+ let(:url) { "users/#{key.actor}/keys/#{key.name}" }
+ let(:key) { user_key }
+ end
+ end
+
+ context "when destroying a client key" do
+ it_should_behave_like "destroy key" do
+ let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" }
+ let(:key) { client_key }
+ end
+ end
+ end
+ end # API Interactions
+end