File Manager
Viewing File: key_spec.rb
#
# Author:: Tyler Cloke (tyler@chef.io)
# Copyright:: Copyright (c) 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 irrelevant 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, {}) }.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::ServerAPI).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).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).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).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).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).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).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
$expected_output = {
actor_type => "foobar",
"name" => key.name,
"create_key" => true,
"expiration_date" => key.expiration_date,
}
$expected_input = {
"name" => key.name,
"create_key" => true,
"expiration_date" => key.expiration_date,
}
end
it "should create a new key via the API" do
expect(rest).to receive(:post).with(url, $expected_input).and_return({})
key.create
end
context "when the server returns the private_key via key.create" do
before do
allow(rest).to receive(:post).with(url, $expected_input).and_return({ "private_key" => "this_private_key" })
end
it "key.create returns the original key plus the private_key" do
expect(key.create.to_hash).to eq($expected_output.merge({ "private_key" => "this_private_key" }))
end
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 }
let(:actor_type) { "user" }
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 }
let(:actor_type) { "client" }
end
end
end # create
describe "update" do
shared_examples_for "update key" do
context "when name is missing and no argument was passed to update" 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).with(url, key.to_hash).and_return({})
key.update
end
end
context "when @name is not nil and a arg is passed to update" do
before do
key.name "new_name"
end
it "passes @name in the body and the arg in the PUT URL" do
expect(rest).to receive(:put).with(update_name_url, key.to_hash).and_return({})
key.update("old_name")
end
end
context "when the server returns a public_key and create_key is true" do
before do
key.name "key_name"
key.create_key true
allow(rest).to receive(:put).with(url, key.to_hash).and_return({
"key" => "key_name",
"public_key" => public_key_string,
})
end
it "returns a key with public_key populated" do
new_key = key.update
expect(new_key.public_key).to eq(public_key_string)
end
it "returns a key without create_key set" do
new_key = key.update
expect(new_key.create_key).to be_nil
end
end
end
context "when updating a user key" do
it_should_behave_like "update key" do
let(:url) { "users/#{key.actor}/keys/#{key.name}" }
let(:update_name_url) { "users/#{key.actor}/keys/old_name" }
let(:key) { user_key }
end
end
context "when updating a client key" do
it_should_behave_like "update key" do
let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" }
let(:update_name_url) { "clients/#{client_key.actor}/keys/old_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).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).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