diff options
author | tylercloke <tylercloke@gmail.com> | 2015-04-07 17:31:34 -0700 |
---|---|---|
committer | tylercloke <tylercloke@gmail.com> | 2015-04-20 15:19:06 -0700 |
commit | 855954929adf26aecaad8cd0f549f03353825d6c (patch) | |
tree | f4da617c866b40e6102cfbe3d2ac936e80802cba /spec/unit/key_spec.rb | |
parent | de7dc03e8da3c30f9b833b25e58f4e087f978c1a (diff) | |
download | chef-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.rb | 577 |
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 |