summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@opscode.com>2021-02-22 10:33:38 -0800
committerGitHub <noreply@github.com>2021-02-22 10:33:38 -0800
commit2e7c4c75e8c0a3c6e6bbf5e3f35068b0577f13ab (patch)
treeb6bed8a9931d7de8e568d2e8acef85d6ff80566b
parente909d2fec12085828ac0198cf715399c91ed72e4 (diff)
parent0570757936debabc9401b22784f6781e3892e21e (diff)
downloadchef-2e7c4c75e8c0a3c6e6bbf5e3f35068b0577f13ab.tar.gz
Merge pull request #10187 from chef/snehal/Merge_knife_plugin_into_Chef
-rw-r--r--lib/chef/group.rb75
-rw-r--r--lib/chef/knife.rb7
-rw-r--r--lib/chef/knife/org_create.rb70
-rw-r--r--lib/chef/knife/org_delete.rb32
-rw-r--r--lib/chef/knife/org_edit.rb48
-rw-r--r--lib/chef/knife/org_list.rb44
-rw-r--r--lib/chef/knife/org_show.rb31
-rw-r--r--lib/chef/knife/org_user_add.rb62
-rw-r--r--lib/chef/knife/org_user_remove.rb103
-rw-r--r--lib/chef/knife/user_create.rb62
-rw-r--r--lib/chef/knife/user_delete.rb115
-rw-r--r--lib/chef/knife/user_edit.rb62
-rw-r--r--lib/chef/knife/user_list.rb8
-rw-r--r--lib/chef/knife/user_password.rb70
-rw-r--r--lib/chef/knife/user_show.rb16
-rw-r--r--lib/chef/org.rb5
-rw-r--r--lib/chef/user.rb1
-rw-r--r--spec/unit/knife/org_create_spec.rb76
-rw-r--r--spec/unit/knife/org_delete_spec.rb41
-rw-r--r--spec/unit/knife/org_edit_spec.rb49
-rw-r--r--spec/unit/knife/org_list_spec.rb58
-rw-r--r--spec/unit/knife/org_show_spec.rb45
-rw-r--r--spec/unit/knife/org_user_add_spec.rb39
-rw-r--r--spec/unit/knife/user_create_spec.rb90
-rw-r--r--spec/unit/knife/user_delete_spec.rb157
-rw-r--r--spec/unit/knife/user_edit_spec.rb18
-rw-r--r--spec/unit/knife/user_list_spec.rb49
-rw-r--r--spec/unit/knife/user_password_spec.rb64
-rw-r--r--spec/unit/knife/user_show_spec.rb79
-rw-r--r--spec/unit/org_group_spec.rb45
30 files changed, 1505 insertions, 116 deletions
diff --git a/lib/chef/group.rb b/lib/chef/group.rb
new file mode 100644
index 0000000000..72f5b7b474
--- /dev/null
+++ b/lib/chef/group.rb
@@ -0,0 +1,75 @@
+#
+# 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_relative "org"
+
+class Chef
+ class Group
+
+ def group(groupname)
+ @group ||= {}
+ @group[groupname] ||= chef_rest.get_rest "organizations/#{name}/groups/#{groupname}"
+ end
+
+ def user_member_of_group?(username, groupname)
+ group = group(groupname)
+ group["actors"].include? username
+ end
+
+ def add_user_to_group(groupname, username)
+ group = group(groupname)
+ body_hash = {
+ groupname: "#{groupname}",
+ actors: {
+ "users" => group["actors"].concat([username]),
+ "groups" => group["groups"],
+ },
+ }
+ chef_rest.put_rest "organizations/#{name}/groups/#{groupname}", body_hash
+ end
+
+ def remove_user_from_group(groupname, username)
+ group = group(groupname)
+ group["actors"].delete(username)
+ body_hash = {
+ groupname: "#{groupname}",
+ actors: {
+ "users" => group["actors"],
+ "groups" => group["groups"],
+ },
+ }
+ chef_rest.put_rest "organizations/#{name}/groups/#{groupname}", body_hash
+ end
+
+ def actor_delete_would_leave_admins_empty?
+ admins = group("admins")
+ if admins["groups"].empty?
+ # exclude 'pivotal' but don't mutate the group since we're caching it
+ if admins["actors"].include? "pivotal"
+ admins["actors"].length <= 2
+ else
+ admins["actors"].length <= 1
+ end
+ else
+ # We don't check recursively. If the admins group contains a group,
+ # and the user is the only member of that group,
+ # we'll still turn up a 'safe to delete'.
+ false
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index ac7a68d0fc..d277e51105 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -661,5 +661,12 @@ class Chef
end
Chef::Config.init_openssl
end
+
+ def root_rest
+ @root_rest ||= begin
+ require_relative "server_api"
+ Chef::ServerAPI.new(Chef::Config[:chef_server_root])
+ end
+ end
end
end
diff --git a/lib/chef/knife/org_create.rb b/lib/chef/knife/org_create.rb
new file mode 100644
index 0000000000..3c1354ae22
--- /dev/null
+++ b/lib/chef/knife/org_create.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Steven Danna (<steve@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.
+#
+
+class Chef
+ class Knife
+ class OrgCreate < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org create ORG_SHORT_NAME ORG_FULL_NAME (options)"
+
+ option :filename,
+ long: "--filename FILENAME",
+ short: "-f FILENAME",
+ description: "Write validator private key to FILENAME rather than STDOUT"
+
+ option :association_user,
+ long: "--association_user USERNAME",
+ short: "-a USERNAME",
+ description: "Invite USERNAME to the new organization after creation"
+
+ attr_accessor :org_name, :org_full_name
+
+ deps do
+ require_relative "../org"
+ end
+
+ def run
+ @org_name, @org_full_name = @name_args
+
+ if !org_name || !org_full_name
+ ui.fatal "You must specify an ORG_NAME and an ORG_FULL_NAME"
+ show_usage
+ exit 1
+ end
+
+ org = Chef::Org.from_hash({ "name" => org_name,
+ "full_name" => org_full_name }).create
+ if config[:filename]
+ File.open(config[:filename], "w") do |f|
+ f.print(org.private_key)
+ end
+ else
+ ui.msg org.private_key
+ end
+
+ if config[:association_user]
+ org.associate_user(config[:association_user])
+ org.add_user_to_group("admins", config[:association_user])
+ org.add_user_to_group("billing-admins", config[:association_user])
+ end
+
+ ui.info("Created #{org_name}")
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/org_delete.rb b/lib/chef/knife/org_delete.rb
new file mode 100644
index 0000000000..340f6c529a
--- /dev/null
+++ b/lib/chef/knife/org_delete.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Steven Danna (<steve@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.
+#
+
+class Chef
+ class Knife
+ class OrgDelete < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org delete ORG_NAME"
+
+ def run
+ org_name = @name_args[0]
+ ui.confirm "Do you want to delete the organization #{org_name}"
+ ui.output root_rest.delete("organizations/#{org_name}")
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/org_edit.rb b/lib/chef/knife/org_edit.rb
new file mode 100644
index 0000000000..1d684ca0b4
--- /dev/null
+++ b/lib/chef/knife/org_edit.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Steven Danna (<steve@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.
+#
+
+class Chef
+ class Knife
+ class OrgEdit < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org edit ORG"
+
+ def run
+ org_name = @name_args[0]
+
+ if org_name.nil?
+ show_usage
+ ui.fatal("You must specify an organization name")
+ exit 1
+ end
+
+ original_org = root_rest.get("organizations/#{org_name}")
+ edited_org = edit_hash(original_org)
+
+ if original_org == edited_org
+ ui.msg("Organization unchanged, not saving.")
+ exit
+ end
+
+ ui.msg edited_org
+ root_rest.put("organizations/#{org_name}", edited_org)
+ ui.msg("Saved #{org_name}.")
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/org_list.rb b/lib/chef/knife/org_list.rb
new file mode 100644
index 0000000000..85a49ee4c5
--- /dev/null
+++ b/lib/chef/knife/org_list.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Steven Danna (<steve@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.
+#
+
+class Chef
+ class Knife
+ class OrgList < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org list"
+
+ option :with_uri,
+ long: "--with-uri",
+ short: "-w",
+ description: "Show corresponding URIs"
+
+ option :all_orgs,
+ long: "--all-orgs",
+ short: "-a",
+ description: "Show auto-generated hidden orgs in output"
+
+ def run
+ results = root_rest.get("organizations")
+ unless config[:all_orgs]
+ results = results.select { |k, v| !(k.length == 20 && k =~ /^[a-z]+$/) }
+ end
+ ui.output(ui.format_list_for_display(results))
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/org_show.rb b/lib/chef/knife/org_show.rb
new file mode 100644
index 0000000000..a8bb207c1d
--- /dev/null
+++ b/lib/chef/knife/org_show.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Steven Danna (<steve@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.
+#
+
+class Chef
+ class Knife
+ class OrgShow < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org show ORGNAME"
+
+ def run
+ org_name = @name_args[0]
+ ui.output root_rest.get("organizations/#{org_name}")
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/org_user_add.rb b/lib/chef/knife/org_user_add.rb
new file mode 100644
index 0000000000..cd0ea88d56
--- /dev/null
+++ b/lib/chef/knife/org_user_add.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Marc Paradise (<marc@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.
+#
+
+class Chef
+ class Knife
+ class OrgUserAdd < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org user add ORG_NAME USER_NAME"
+ attr_accessor :org_name, :username
+
+ option :admin,
+ long: "--admin",
+ short: "-a",
+ description: "Add user to admin group"
+
+ deps do
+ require_relative "../org"
+ end
+
+ def run
+ @org_name, @username = @name_args
+
+ if !org_name || !username
+ ui.fatal "You must specify an ORG_NAME and USER_NAME"
+ show_usage
+ exit 1
+ end
+
+ org = Chef::Org.new(@org_name)
+ begin
+ org.associate_user(@username)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ ui.msg "User #{username} already associated with organization #{org_name}"
+ else
+ raise e
+ end
+ end
+ if config[:admin]
+ org.add_user_to_group("admins", @username)
+ org.add_user_to_group("billing-admins", @username)
+ ui.msg "User #{username} is added to admins and billing-admins group"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/org_user_remove.rb b/lib/chef/knife/org_user_remove.rb
new file mode 100644
index 0000000000..50a1471443
--- /dev/null
+++ b/lib/chef/knife/org_user_remove.rb
@@ -0,0 +1,103 @@
+#
+# Author:: Marc Paradise (<marc@getchef.com>)
+# 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.
+#
+
+class Chef
+ class Knife
+ class OrgUserRemove < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org user remove ORG_NAME USER_NAME"
+ attr_accessor :org_name, :username
+
+ option :force_remove_from_admins,
+ long: "--force",
+ short: "-f",
+ description: "Force removal of user from the organization's admins and billing-admins group."
+
+ deps do
+ require_relative "../org"
+ require "chef/json_compat"
+ end
+
+ def run
+ @org_name, @username = @name_args
+
+ if !org_name || !username
+ ui.fatal "You must specify an ORG_NAME and USER_NAME"
+ show_usage
+ exit 1
+ end
+
+ org = Chef::Org.new(@org_name)
+
+ if config[:force_remove_from_admins]
+ if org.actor_delete_would_leave_admins_empty?
+ failure_error_message(org_name, username)
+ ui.msg <<~EOF
+ You ran with --force which force removes the user from the admins and billing-admins groups.
+ However, removing #{username} from the admins group would leave it empty, which breaks the org.
+ Please add another user to org #{org_name} admins group and try again.
+ EOF
+ exit 1
+ end
+ remove_user_from_admin_group(org, org_name, username, "admins")
+ remove_user_from_admin_group(org, org_name, username, "billing-admins")
+ end
+
+ begin
+ org.dissociate_user(@username)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ ui.msg "User #{username} is not associated with organization #{org_name}"
+ exit 1
+ elsif e.response.code == "403"
+ body = Chef::JSONCompat.from_json(e.response.body)
+ if body.key?("error") && body["error"] == "Please remove #{username} from this organization's admins group before removing him or her from the organization."
+ failure_error_message(org_name, username)
+ ui.msg <<~EOF
+ User #{username} is in the organization's admin group. Removing users from an organization without removing them from the admins group is not allowed.
+ Re-run this command with --force to remove this user from the admins prior to removing it from the organization.
+ EOF
+ exit 1
+ else
+ raise e
+ end
+ else
+ raise e
+ end
+ end
+ end
+
+ def failure_error_message(org_name, username)
+ ui.error "Error removing user #{username} from organization #{org_name}."
+ end
+
+ def remove_user_from_admin_group(org, org_name, username, admin_group_string)
+ org.remove_user_from_group(admin_group_string, username)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ ui.warn <<~EOF
+ User #{username} is not in the #{admin_group_string} group for organization #{org_name}.
+ You probably don't need to pass --force.
+ EOF
+ else
+ raise e
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index 6d68f3ebbb..aa1d4d54f2 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -1,5 +1,4 @@
#
-# Author:: Steven Danna (<steve@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
@@ -45,16 +44,22 @@ class Chef
description: "API V1 (#{ChefUtils::Dist::Server::PRODUCT} 12.1+) only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
boolean: true
+ option :orgname,
+ long: "--orgname ORGNAME",
+ short: "-o ORGNAME",
+ description: "Associate new user to an organization matching ORGNAME"
+
+ option :passwordprompt,
+ long: "--prompt-for-password",
+ short: "-p",
+ description: "Prompt for user password"
+
banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
def user
@user_field ||= Chef::UserV1.new
end
- def create_user_from_hash(hash)
- Chef::UserV1.from_hash(hash).create
- end
-
def run
test_mandatory_field(@name_args[0], "username")
user.username @name_args[0]
@@ -71,8 +76,11 @@ class Chef
test_mandatory_field(@name_args[4], "email")
user.email @name_args[4]
- test_mandatory_field(@name_args[5], "password")
- user.password @name_args[5]
+ password = config[:passwordprompt] ? prompt_for_password : @name_args[5]
+ unless password
+ ui.fatal "You must either provide a password or use the --prompt-for-password (-p) option"
+ exit 1
+ end
if config[:user_key] && config[:prevent_keygen]
show_usage
@@ -88,20 +96,48 @@ class Chef
user.public_key File.read(File.expand_path(config[:user_key]))
end
- output = edit_hash(user)
- final_user = create_user_from_hash(output)
+ user_hash = {
+ username: user.username,
+ first_name: user.first_name,
+ last_name: user.last_name,
+ display_name: "#{user.first_name} #{user.last_name}",
+ email: user.email,
+ password: password,
+ }
+
+ # Check the file before creating the user so the api is more transactional.
+ if config[:file]
+ file = config[:file]
+ unless File.exist?(file) ? File.writable?(file) : File.writable?(File.dirname(file))
+ ui.fatal "File #{config[:file]} is not writable. Check permissions."
+ exit 1
+ end
+ end
+
+ final_user = root_rest.post("users/", user_hash)
+
+ if config[:orgname]
+ request_body = { user: user.username }
+ response = root_rest.post("organizations/#{config[:orgname]}/association_requests", request_body)
+ association_id = response["uri"].split("/").last
+ root_rest.put("users/#{user.username}/association_requests/#{association_id}", { response: "accept" })
+ end
- ui.info("Created #{user}")
- if final_user.private_key
+ ui.info("Created #{user.username}")
+ if final_user["private_key"]
if config[:file]
File.open(config[:file], "w") do |f|
- f.print(final_user.private_key)
+ f.print(final_user["private_key"])
end
else
- ui.msg final_user.private_key
+ ui.msg final_user["private_key"]
end
end
end
+
+ def prompt_for_password
+ ui.ask("Please enter the user's password: ", echo: false)
+ end
end
end
end
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
index 87c1f734bb..64d729c951 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -23,21 +23,128 @@ class Chef
class UserDelete < Knife
deps do
- require_relative "../user_v1"
+ require_relative "../org"
end
banner "knife user delete USER (options)"
+ option :no_disassociate_user,
+ long: "--no-disassociate-user",
+ short: "-d",
+ description: "Don't disassociate the user first"
+
+ option :remove_from_admin_groups,
+ long: "--remove-from-admin-groups",
+ short: "-R",
+ description: "If the user is a member of any org admin groups, attempt to remove from those groups. Ignored if --no-disassociate-user is set."
+
+ attr_reader :username
+
def run
- @user_name = @name_args[0]
+ @username = @name_args[0]
+ admin_memberships = []
+ unremovable_memberships = []
- if @user_name.nil?
+ if @username.nil?
show_usage
ui.fatal("You must specify a user name")
exit 1
end
- delete_object(Chef::UserV1, @user_name)
+ ui.confirm "Do you want to delete the user #{username}"
+
+ unless config[:no_disassociate_user]
+ ui.stderr.puts("Checking organization memberships...")
+ orgs = org_memberships(username)
+ if orgs.length > 0
+ ui.stderr.puts("Checking admin group memberships for #{orgs.length} org(s).")
+ admin_memberships, unremovable_memberships = admin_group_memberships(orgs, username)
+ end
+
+ unless admin_memberships.empty?
+ unless config[:remove_from_admin_groups]
+ error_exit_admin_group_member!(username, admin_memberships)
+ end
+
+ unless unremovable_memberships.empty?
+ error_exit_cant_remove_admin_membership!(username, unremovable_memberships)
+ end
+ remove_from_admin_groups(admin_memberships, username)
+ end
+ disassociate_user(orgs, username)
+ end
+
+ delete_user(username)
+ end
+
+ def disassociate_user(orgs, username)
+ orgs.each { |org| org.dissociate_user(username) }
+ end
+
+ def org_memberships(username)
+ org_data = root_rest.get("users/#{username}/organizations")
+ org_data.map { |org| Chef::Org.new(org["organization"]["name"]) }
+ end
+
+ def remove_from_admin_groups(admin_of, username)
+ admin_of.each do |org|
+ ui.stderr.puts "Removing #{username} from admins group of '#{org.name}'"
+ org.remove_user_from_group("admins", username)
+ end
+ end
+
+ def admin_group_memberships(orgs, username)
+ admin_of = []
+ unremovable = []
+ orgs.each do |org|
+ if org.user_member_of_group?(username, "admins")
+ admin_of << org
+ if org.actor_delete_would_leave_admins_empty?
+ unremovable << org
+ end
+ end
+ end
+ [admin_of, unremovable]
+ end
+
+ def delete_user(username)
+ ui.stderr.puts "Deleting user #{username}."
+ root_rest.delete("users/#{username}")
+ end
+
+ # Error message that says how to removed from org
+ # admin groups before deleting
+ # Further
+ def error_exit_admin_group_member!(username, admin_of)
+ message = "#{username} is in the 'admins' group of the following organization(s):\n\n"
+ admin_of.each { |org| message << "- #{org.name}\n" }
+ message << <<~EOM
+
+ Run this command again with the --remove-from-admin-groups option to
+ remove the user from these admin group(s) automatically.
+
+ EOM
+ ui.fatal message
+ exit 1
+ end
+
+ def error_exit_cant_remove_admin_membership!(username, only_admin_of)
+ message = <<~EOM
+
+ #{username} is the only member of the 'admins' group of the
+ following organization(s):
+
+ EOM
+ only_admin_of.each { |org| message << "- #{org.name}\n" }
+ message << <<~EOM
+
+ Removing the only administrator of an organization can break it.
+ Assign additional users or groups to the admin group(s) before
+ deleting this user.
+
+ EOM
+ ui.fatal message
+ exit 1
end
end
end
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index ad9dfac079..fff8c6b70f 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -22,12 +22,18 @@ class Chef
class Knife
class UserEdit < Knife
- deps do
- require_relative "../user_v1"
- end
-
banner "knife user edit USER (options)"
+ option :input,
+ long: "--input FILENAME",
+ short: "-i FILENAME",
+ description: "Name of file to use for PUT or POST"
+
+ option :filename,
+ long: "--filename FILENAME",
+ short: "-f FILENAME",
+ description: "Write private key to FILENAME rather than STDOUT"
+
def run
@user_name = @name_args[0]
@@ -36,17 +42,53 @@ class Chef
ui.fatal("You must specify a user name")
exit 1
end
-
- original_user = Chef::UserV1.load(@user_name).to_hash
- edited_user = edit_hash(original_user)
+ original_user = root_rest.get("users/#{@user_name}")
+ edited_user = get_updated_user(original_user)
if original_user != edited_user
- user = Chef::UserV1.from_hash(edited_user)
- user.update
- ui.msg("Saved #{user}.")
+ result = root_rest.put("users/#{@user_name}", edited_user)
+ ui.msg("Saved #{@user_name}.")
+ unless result["private_key"].nil?
+ if config[:filename]
+ File.open(config[:filename], "w") do |f|
+ f.print(result["private_key"])
+ end
+ else
+ ui.msg result["private_key"]
+ end
+ end
else
ui.msg("User unchanged, not saving.")
end
end
end
+
+ private
+
+ # Check the options for ex: input or filename
+ # Read Or Open file to update user information
+ # return updated user
+ def get_updated_user(original_user)
+ if config[:input]
+ edited_user = JSON.parse(IO.read(config[:input]))
+ elsif config[:filename]
+ file = config[:filename]
+ unless File.exist?(file) ? File.writable?(file) : File.writable?(File.dirname(file))
+ ui.fatal "File #{file} is not writable. Check permissions."
+ exit 1
+ else
+ output = Chef::JSONCompat.to_json_pretty(original_user)
+ File.open(file, "w") do |f|
+ f.sync = true
+ f.puts output
+ f.close
+ raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details." unless system("#{config[:editor]} #{f.path}")
+
+ edited_user = JSON.parse(IO.read(f.path))
+ end
+ end
+ else
+ edited_user = JSON.parse(edit_data(original_user, false))
+ end
+ end
end
end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
index f6aa7bcfc4..3284964a47 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -22,10 +22,6 @@ class Chef
class Knife
class UserList < Knife
- deps do
- require_relative "../user_v1"
- end
-
banner "knife user list (options)"
option :with_uri,
@@ -34,9 +30,9 @@ class Chef
description: "Show corresponding URIs."
def run
- output(format_list_for_display(Chef::UserV1.list))
+ results = root_rest.get("users")
+ output(format_list_for_display(results))
end
-
end
end
end
diff --git a/lib/chef/knife/user_password.rb b/lib/chef/knife/user_password.rb
new file mode 100644
index 0000000000..2da3c3e285
--- /dev/null
+++ b/lib/chef/knife/user_password.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Tyler Cloke (<tyler@getchef.com>)
+# 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.
+#
+
+class Chef
+ class Knife
+ class UserPassword < Knife
+ banner "knife user password USERNAME [PASSWORD | --enable-external-auth]"
+
+ option :enable_external_auth,
+ long: "--enable-external-auth",
+ short: "-e",
+ description: "Enable external authentication for this user (such as LDAP)"
+
+ def run
+ # check that correct number of args was passed, should be either
+ # USERNAME PASSWORD or USERNAME --enable-external-auth
+ #
+ # note that you can't pass USERNAME PASSWORD --enable-external-auth
+ unless (@name_args.length == 2 && !config[:enable_external_auth]) || (@name_args.length == 1 && config[:enable_external_auth])
+ show_usage
+ ui.fatal("You must pass two arguments")
+ ui.fatal("Note that --enable-external-auth cannot be passed with a password")
+ exit 1
+ end
+
+ user_name = @name_args[0]
+
+ # note that this will be nil if config[:enable_external_auth] is true
+ password = @name_args[1]
+
+ # since the API does not pass back whether recovery_authentication_enabled is
+ # true or false, there is no way of knowing if the user is using ldap or not,
+ # so we will update the user every time, instead of checking if we are actually
+ # changing anything before we PUT.
+ result = root_rest.get("users/#{user_name}")
+
+ result["password"] = password unless password.nil?
+
+ # if --enable-external-auth was passed, enable it, else disable it.
+ # there is never a situation where we would want to enable ldap
+ # AND change the password. changing the password means that the user
+ # wants to disable ldap and put user in recover (if they are using ldap).
+ result["recovery_authentication_enabled"] = !config[:enable_external_auth]
+
+ begin
+ root_rest.put("users/#{user_name}", result)
+ rescue => e
+ raise e
+ end
+
+ ui.msg("Authentication info updated for #{user_name}.")
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
index e59f969e9a..ea2b06b753 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -24,12 +24,12 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
- deps do
- require_relative "../user_v1"
- end
-
banner "knife user show USER (options)"
+ option :with_orgs,
+ long: "--with-orgs",
+ short: "-l"
+
def run
@user_name = @name_args[0]
@@ -39,8 +39,12 @@ class Chef
exit 1
end
- user = Chef::UserV1.load(@user_name)
- output(format_for_display(user))
+ results = root_rest.get("users/#{@user_name}")
+ if config[:with_orgs]
+ orgs = root_rest.get("users/#{@user_name}/organizations")
+ results["organizations"] = orgs.map { |o| o["organization"]["name"] }
+ end
+ output(format_for_display(results))
end
end
diff --git a/lib/chef/org.rb b/lib/chef/org.rb
index e2b7c49051..8f65f3ddd1 100644
--- a/lib/chef/org.rb
+++ b/lib/chef/org.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (steve@chef.io)
-# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,10 @@
require_relative "json_compat"
require_relative "mixin/params_validate"
require_relative "server_api"
+require_relative "group"
class Chef
- class Org
+ class Org < Group
include Chef::Mixin::ParamsValidate
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index e578cc2131..4ebcb3a463 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -36,7 +36,6 @@ require_relative "server_api"
# should be removed once client support for Open Source Chef Server 11 expires.
class Chef
class User
-
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
diff --git a/spec/unit/knife/org_create_spec.rb b/spec/unit/knife/org_create_spec.rb
new file mode 100644
index 0000000000..3c33817b55
--- /dev/null
+++ b/spec/unit/knife/org_create_spec.rb
@@ -0,0 +1,76 @@
+#
+# Copyright:: Copyright 2014-2016 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/org"
+
+describe Chef::Knife::OrgCreate do
+ before :each do
+ Chef::Knife::OrgCreate.load_deps
+ @knife = Chef::Knife::OrgCreate.new
+ @org = double("Chef::Org")
+ allow(Chef::Org).to receive(:new).and_return(@org)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@org).to receive(:private_key).and_return(@key)
+ @org_name = "ss"
+ @org_full_name = "secretsauce"
+ end
+
+ let(:org_args) do
+ {
+ name: @org_name,
+ full_name: @org_full_name,
+ }
+ end
+
+ describe "with no org_name and org_fullname" do
+ it "fails with an informative message" do
+ expect(@knife.ui).to receive(:fatal).with("You must specify an ORG_NAME and an ORG_FULL_NAME")
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with org_name and org_fullname" do
+ before :each do
+ @knife.name_args << @org_name << @org_full_name
+ end
+
+ it "creates an org" do
+ expect(@org).to receive(:create).and_return(@org)
+ expect(@org).to receive(:full_name).with("secretsauce")
+ expect(@knife.ui).to receive(:msg).with(@key)
+ @knife.run
+ end
+
+ context "with --assocation-user" do
+ before :each do
+ @knife.config[:association_user] = "ramsay"
+ end
+
+ it "creates an org, associates a user, and adds it to the admins group" do
+ expect(@org).to receive(:full_name).with("secretsauce")
+ expect(@org).to receive(:create).and_return(@org)
+ expect(@org).to receive(:associate_user).with("ramsay")
+ expect(@org).to receive(:add_user_to_group).with("admins", "ramsay")
+ expect(@org).to receive(:add_user_to_group).with("billing-admins", "ramsay")
+ expect(@knife.ui).to receive(:msg).with(@key)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/spec/unit/knife/org_delete_spec.rb b/spec/unit/knife/org_delete_spec.rb
new file mode 100644
index 0000000000..baa102f8c8
--- /dev/null
+++ b/spec/unit/knife/org_delete_spec.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# 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/org"
+
+describe Chef::Knife::OrgDelete do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @knife = Chef::Knife::OrgDelete.new
+ @org_name = "foobar"
+ @org_full_name = "secretsauce"
+ @knife.name_args << @org_name
+ @org = double("Chef::Org")
+ end
+
+ it "should confirm that you want to delete and then delete organizations" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(root_rest)
+ expect(@knife.ui).to receive(:confirm).with("Do you want to delete the organization #{@org_name}")
+ expect(root_rest).to receive(:delete).with("organizations/#{@org_name}")
+ expect(@knife.ui).to receive(:output)
+ @knife.run
+ end
+end
diff --git a/spec/unit/knife/org_edit_spec.rb b/spec/unit/knife/org_edit_spec.rb
new file mode 100644
index 0000000000..05339e8f21
--- /dev/null
+++ b/spec/unit/knife/org_edit_spec.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# 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"
+
+describe Chef::Knife::OrgEdit do
+ let(:knife) { Chef::Knife::OrgEdit.new }
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ Chef::Knife::OrgEdit.load_deps
+ @org_name = "foobar"
+ knife.name_args << @org_name
+ @org = double("Chef::Org")
+ knife.config[:disable_editing] = true
+ end
+
+ it "loads and edits the organisation" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ original_data = { "org_name" => "my_org" }
+ data = { "org_name" => "my_org1" }
+ expect(root_rest).to receive(:get).with("organizations/foobar").and_return(original_data)
+ expect(knife).to receive(:edit_hash).with(original_data).and_return(data)
+ expect(root_rest).to receive(:put).with("organizations/foobar", data)
+ knife.run
+ end
+
+ it "prints usage and exits when a org name is not provided" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+end
diff --git a/spec/unit/knife/org_list_spec.rb b/spec/unit/knife/org_list_spec.rb
new file mode 100644
index 0000000000..de77b4b0c7
--- /dev/null
+++ b/spec/unit/knife/org_list_spec.rb
@@ -0,0 +1,58 @@
+#
+# 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/org"
+
+describe Chef::Knife::OrgList do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ let(:orgs) do
+ {
+ "org1" => "first",
+ "org2" => "second",
+ "hiddenhiddenhiddenhi" => "hidden",
+ }
+ end
+
+ before :each do
+ @org = double("Chef::Org")
+ @knife = Chef::Knife::OrgList.new
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ allow(root_rest).to receive(:get).with("organizations").and_return(orgs)
+ end
+
+ describe "with no arguments" do
+ it "lists all non hidden orgs" do
+ expect(@knife.ui).to receive(:output).with(%w{org1 org2})
+ @knife.run
+ end
+
+ end
+
+ describe "with all_orgs argument" do
+ before do
+ @knife.config[:all_orgs] = true
+ end
+
+ it "lists all orgs including hidden orgs" do
+ expect(@knife.ui).to receive(:output).with(%w{hiddenhiddenhiddenhi org1 org2})
+ @knife.run
+ end
+ end
+end
diff --git a/spec/unit/knife/org_show_spec.rb b/spec/unit/knife/org_show_spec.rb
new file mode 100644
index 0000000000..2f5246dd84
--- /dev/null
+++ b/spec/unit/knife/org_show_spec.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# 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/org"
+
+describe Chef::Knife::OrgShow do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @knife = Chef::Knife::OrgShow.new
+ @org_name = "foobar"
+ @knife.name_args << @org_name
+ @org = double("Chef::Org")
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ allow(@org).to receive(:root_rest).and_return(root_rest)
+ end
+
+ it "should load the organisation" do
+ expect(root_rest).to receive(:get).with("organizations/#{@org_name}")
+ @knife.run
+ end
+
+ it "should pretty print the output organisation" do
+ expect(root_rest).to receive(:get).with("organizations/#{@org_name}")
+ expect(@knife.ui).to receive(:output)
+ @knife.run
+ end
+end
diff --git a/spec/unit/knife/org_user_add_spec.rb b/spec/unit/knife/org_user_add_spec.rb
new file mode 100644
index 0000000000..20e28d6919
--- /dev/null
+++ b/spec/unit/knife/org_user_add_spec.rb
@@ -0,0 +1,39 @@
+#
+# 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/org"
+
+describe Chef::Knife::OrgUserAdd do
+ context "with --admin" do
+ subject(:knife) { Chef::Knife::OrgUserAdd.new }
+ let(:org) { double("Chef::Org") }
+
+ it "adds the user to admins and billing-admins groups" do
+ allow(Chef::Org).to receive(:new).and_return(org)
+
+ knife.config[:admin] = true
+ knife.name_args = %w{testorg testuser}
+
+ expect(org).to receive(:associate_user).with("testuser")
+ expect(org).to receive(:add_user_to_group).with("admins", "testuser")
+ expect(org).to receive(:add_user_to_group).with("billing-admins", "testuser")
+
+ knife.run
+ end
+ end
+end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
index be3d2fd99c..c69a668f7e 100644
--- a/spec/unit/knife/user_create_spec.rb
+++ b/spec/unit/knife/user_create_spec.rb
@@ -22,7 +22,9 @@ require "spec_helper"
Chef::Knife::UserCreate.load_deps
describe Chef::Knife::UserCreate do
+
let(:knife) { Chef::Knife::UserCreate.new }
+ let(:root_rest) { double("Chef::ServerAPI") }
let(:stderr) do
StringIO.new
@@ -38,6 +40,8 @@ describe Chef::Knife::UserCreate do
allow(knife.ui).to receive(:warn)
end
+ let(:chef_root_rest_v0) { double("Chef::ServerAPI") }
+
context "when USERNAME isn't specified" do
# from spec/support/shared/unit/knife_shared.rb
it_should_behave_like "mandatory field missing" do
@@ -70,34 +74,42 @@ describe Chef::Knife::UserCreate do
end
end
- context "when PASSWORD isn't specified" do
- # from spec/support/shared/unit/knife_shared.rb
- it_should_behave_like "mandatory field missing" do
- let(:name_args) { %w{some_user some_display_name some_first_name some_last_name some_email} }
- let(:fieldname) { "password" }
+ describe "with prompt password" do
+ let(:name_args) { %w{some_user some_display_name some_first_name some_last_name test@email.com} }
+
+ before :each do
+ @user = double("Chef::User")
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@user).to receive(:[]).with("private_key").and_return(@key)
+ knife.config[:passwordprompt] = true
+ knife.name_args = name_args
+ end
+
+ it "creates an user" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ expect(knife.ui).to receive(:ask).with("Please enter the user's password: ", echo: false).and_return("password")
+ knife.run
end
end
context "when all mandatory fields are validly specified" do
before do
+ @user = double("Chef::User")
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@user).to receive(:[]).with("private_key").and_return(@key)
knife.name_args = %w{some_user some_display_name some_first_name some_last_name some_email some_password}
- allow(knife).to receive(:edit_hash).and_return(knife.user.to_hash)
- allow(knife).to receive(:create_user_from_hash).and_return(knife.user)
- end
-
- before(:each) do
- # reset the user field every run
- knife.user_field = nil
end
it "sets all the mandatory fields" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
knife.run
expect(knife.user.username).to eq("some_user")
expect(knife.user.display_name).to eq("some_display_name")
expect(knife.user.first_name).to eq("some_first_name")
expect(knife.user.last_name).to eq("some_last_name")
expect(knife.user.email).to eq("some_email")
- expect(knife.user.password).to eq("some_password")
end
context "when user_key and prevent_keygen are passed" do
@@ -105,6 +117,7 @@ describe Chef::Knife::UserCreate do
knife.config[:user_key] = "some_key"
knife.config[:prevent_keygen] = true
end
+
it "prints the usage" do
expect(knife).to receive(:show_usage)
expect { knife.run }.to raise_error(SystemExit)
@@ -122,6 +135,8 @@ describe Chef::Knife::UserCreate do
end
it "does not set user.create_key" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
knife.run
expect(knife.user.create_key).to be_falsey
end
@@ -129,6 +144,8 @@ describe Chef::Knife::UserCreate do
context "when --prevent-keygen is not passed" do
it "sets user.create_key to true" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
knife.run
expect(knife.user.create_key).to be_truthy
end
@@ -142,6 +159,8 @@ describe Chef::Knife::UserCreate do
end
it "sets user.public_key" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
knife.run
expect(knife.user.public_key).to eq("some_key")
end
@@ -149,32 +168,45 @@ describe Chef::Knife::UserCreate do
context "when --user-key is not passed" do
it "does not set user.public_key" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
knife.run
expect(knife.user.public_key).to be_nil
end
end
- context "when a private_key is returned" do
- before do
- allow(knife).to receive(:create_user_from_hash).and_return(Chef::UserV1.from_hash(knife.user.to_hash.merge({ "private_key" => "some_private_key" })))
+ describe "with user_name, first_name, last_name, email and password" do
+ let(:name_args) { %w{some_user some_display_name some_first_name some_last_name test@email.com some_password} }
+
+ before :each do
+ @user = double("Chef::User")
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@user).to receive(:[]).with("private_key").and_return(@key)
+ knife.name_args = name_args
end
- context "when --file is passed" do
- before do
- knife.config[:file] = "/some/path"
- end
+ it "creates an user" do
+ expect(knife.ui).to receive(:msg).with(@key)
+ knife.run
+ end
- it "creates a new file of the path passed" do
- filehandle = double("filehandle")
- expect(filehandle).to receive(:print).with("some_private_key")
- expect(File).to receive(:open).with("/some/path", "w").and_yield(filehandle)
- knife.run
+ context "with --orgname" do
+ before :each do
+ knife.config[:orgname] = "ramsay"
+ @uri = "http://www.example.com/1"
+ allow(@user).to receive(:[]).with("uri").and_return(@uri)
end
- end
- context "when --file is not passed" do
- it "prints the private key to stdout" do
- expect(knife.ui).to receive(:msg).with("some_private_key")
+ let(:request_body) {
+ { user: "some_user" }
+ }
+
+ it "creates an user, associates a user, and adds it to the admins group" do
+
+ expect(root_rest).to receive(:post).with("organizations/ramsay/association_requests", request_body).and_return(@user)
+ expect(root_rest).to receive(:put).with("users/some_user/association_requests/1", { response: "accept" })
knife.run
end
end
diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb
index 959d792b9e..4dd2665cda 100644
--- a/spec/unit/knife/user_delete_spec.rb
+++ b/spec/unit/knife/user_delete_spec.rb
@@ -17,30 +17,155 @@
#
require "spec_helper"
+require "chef/org"
+
+Chef::Knife::UserDelete.load_deps
describe Chef::Knife::UserDelete do
+ subject(:knife) { Chef::Knife::UserDelete.new }
+
+ let(:non_admin_member_org) { Chef::Org.new("non-admin-member") }
+ let(:solo_admin_member_org) { Chef::Org.new("solo-admin-member") }
+ let(:shared_admin_member_org) { Chef::Org.new("shared-admin-member") }
+
+ let(:removable_orgs) { [non_admin_member_org, shared_admin_member_org] }
+ let(:non_removable_orgs) { [solo_admin_member_org] }
+
+ let(:admin_memberships) { [ removable_orgs, non_removable_orgs ] }
+ let(:username) { "test_user" }
+
+ let(:rest) { double("Chef::ServerAPI") }
+ let(:orgs) { [non_admin_member_org, solo_admin_member_org, shared_admin_member_org] }
let(:knife) { Chef::Knife::UserDelete.new }
- let(:user) { double("user_object") }
- let(:stdout) { StringIO.new }
+
+ let(:orgs_data) do
+ [{ "organization" => { "name" => "non-admin-member" } },
+ { "organization" => { "name" => "solo-admin-member" } },
+ { "organization" => { "name" => "shared-admin-member" } },
+ ]
+ end
before(:each) do
- Chef::Knife::UserDelete.load_deps
- knife.name_args = [ "my_user" ]
- allow(Chef::UserV1).to receive(:load).and_return(user)
- allow(user).to receive(:username).and_return("my_user")
- allow(knife.ui).to receive(:stderr).and_return(stdout)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(Chef::ServerAPI).to receive(:new).and_return(rest)
+ knife.name_args << username
+ knife.config[:yes] = true
+ end
+
+ context "when invoked" do
+ before do
+ allow(knife).to receive(:admin_group_memberships).and_return(admin_memberships)
+ end
+
+ context "with --no-disassociate-user" do
+ before(:each) do
+ knife.config[:no_disassociate_user] = true
+ end
+
+ it "should bypass all checks and go directly to user deletion" do
+ expect(knife).to receive(:delete_user).with(username)
+ knife.run
+ end
+ end
+
+ context "without --no-disassociate-user" do
+ before do
+ allow(knife).to receive(:org_memberships).and_return(orgs)
+ end
+
+ context "and with --remove-from-admin-groups" do
+ let(:non_removable_orgs) { [ solo_admin_member_org ] }
+ before(:each) do
+ knife.config[:remove_from_admin_groups] = true
+ end
+
+ context "when an associated user the only organization admin" do
+ let(:non_removable_orgs) { [ solo_admin_member_org ] }
+
+ it "refuses to proceed with because the user is the only admin" do
+ expect(knife).to receive(:error_exit_cant_remove_admin_membership!).and_call_original
+ expect { knife.run }.to raise_error SystemExit
+ end
+ end
+
+ context "when an associated user is one of many organization admins" do
+ let(:non_removable_orgs) { [] }
+
+ it "should remove the user from the group, the org, and then and delete the user" do
+ expect(knife).to receive(:disassociate_user)
+ expect(knife).to receive(:remove_from_admin_groups)
+ expect(knife).to receive(:delete_user)
+ expect(knife).to receive(:error_exit_cant_remove_admin_membership!).exactly(0).times
+ expect(knife).to receive(:error_exit_admin_group_member!).exactly(0).times
+ knife.run
+ end
+
+ end
+ end
+
+ context "and without --remove-from-admin-groups" do
+ before(:each) do
+ knife.config[:remove_from_admin_groups] = false
+ end
+
+ context "when an associated user is in admins group" do
+ let(:removable_orgs) { [ shared_admin_member_org ] }
+ let(:non_removable_orgs) { [ ] }
+ it "refuses to proceed with because the user is an admin" do
+ # Default setup
+ expect(knife).to receive(:error_exit_admin_group_member!).and_call_original
+ expect { knife.run }.to raise_error SystemExit
+ end
+ end
+ end
+
+ end
+ end
+
+ context "#admin_group_memberships" do
+ before do
+ expect(non_admin_member_org).to receive(:user_member_of_group?).and_return false
+
+ expect(solo_admin_member_org).to receive(:user_member_of_group?).and_return true
+ expect(solo_admin_member_org).to receive(:actor_delete_would_leave_admins_empty?).and_return true
+
+ expect(shared_admin_member_org).to receive(:user_member_of_group?).and_return true
+ expect(shared_admin_member_org).to receive(:actor_delete_would_leave_admins_empty?).and_return false
+
+ end
+
+ it "returns an array of organizations in which the user is an admin, and an array of orgs which block removal" do
+ expect(knife.admin_group_memberships(orgs, username)).to eq [ [solo_admin_member_org, shared_admin_member_org], [solo_admin_member_org]]
+ end
+ end
+
+ context "#delete_user" do
+ it "attempts to delete the user from the system via DELETE to the /users endpoint" do
+ expect(rest).to receive(:delete).with("users/#{username}")
+ knife.delete_user(username)
+ end
+ end
+
+ context "#disassociate_user" do
+ it "attempts to remove dissociate the user from each org" do
+ removable_orgs.each { |org| expect(org).to receive(:dissociate_user).with(username) }
+ knife.disassociate_user(removable_orgs, username)
+ end
end
- it "deletes the user" do
- expect(knife).to receive(:delete_object).with(Chef::UserV1, "my_user")
- knife.run
+ context "#remove_from_admin_groups" do
+ it "attempts to remove the given user from the organizations' groups" do
+ removable_orgs.each { |org| expect(org).to receive(:remove_user_from_group).with("admins", username) }
+ knife.remove_from_admin_groups(removable_orgs, username)
+ end
end
- it "prints usage and exits when a user name is not provided" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
+ context "#org_memberships" do
+ it "should make a REST request to return the list of organizations that the user is a member of" do
+ expect(rest).to receive(:get).with("users/test_user/organizations").and_return orgs_data
+ result = knife.org_memberships(username)
+ result.each_with_index do |v, x|
+ expect(v.to_hash).to eq(orgs[x].to_hash)
+ end
+ end
end
end
diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb
index 54a44890e0..2fde328c0c 100644
--- a/spec/unit/knife/user_edit_spec.rb
+++ b/spec/unit/knife/user_edit_spec.rb
@@ -20,22 +20,28 @@ require "spec_helper"
describe Chef::Knife::UserEdit do
let(:knife) { Chef::Knife::UserEdit.new }
+ let(:root_rest) { double("Chef::ServerAPI") }
before(:each) do
@stderr = StringIO.new
@stdout = StringIO.new
-
- Chef::Knife::UserEdit.load_deps
allow(knife.ui).to receive(:stderr).and_return(@stderr)
allow(knife.ui).to receive(:stdout).and_return(@stdout)
- knife.name_args = [ "my_user" ]
+ knife.name_args = [ "my_user2" ]
knife.config[:disable_editing] = true
end
it "loads and edits the user" do
- data = { "username" => "my_user" }
- allow(Chef::UserV1).to receive(:load).with("my_user").and_return(data)
- expect(knife).to receive(:edit_hash).with(data).and_return(data)
+ data = { "username" => "my_user2" }
+ edited_data = { "username" => "edit_user2" }
+ result = {}
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(result).to receive(:[]).with("private_key").and_return(@key)
+
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:get).with("users/my_user2").and_return(data)
+ expect(knife).to receive(:get_updated_user).with(data).and_return(edited_data)
+ expect(root_rest).to receive(:put).with("users/my_user2", edited_data).and_return(result)
knife.run
end
diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb
index 21c07f3fb1..63df590591 100644
--- a/spec/unit/knife/user_list_spec.rb
+++ b/spec/unit/knife/user_list_spec.rb
@@ -18,19 +18,56 @@
require "spec_helper"
+Chef::Knife::UserList.load_deps
+
describe Chef::Knife::UserList do
+
let(:knife) { Chef::Knife::UserList.new }
- let(:stdout) { StringIO.new }
+ let(:users) do
+ {
+ "user1" => "http//test/users/user1",
+ "user2" => "http//test/users/user2",
+ }
+ end
+
+ before :each do
+ @rest = double("Chef::ServerAPI")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@rest)
+ allow(@rest).to receive(:get).with("users").and_return(users)
+ end
+
+ describe "with no arguments" do
+ it "lists all non users" do
+ expect(knife.ui).to receive(:output).with(%w{user1 user2})
+ knife.run
+ end
- before(:each) do
- Chef::Knife::UserList.load_deps
- allow(knife.ui).to receive(:stderr).and_return(stdout)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
+ end
+
+ describe "with all_users argument" do
+ before do
+ knife.config[:all_users] = true
+ end
+
+ it "lists all users including hidden users" do
+ expect(knife.ui).to receive(:output).with(%w{user1 user2})
+ knife.run
+ end
end
it "lists the users" do
- expect(Chef::UserV1).to receive(:list)
expect(knife).to receive(:format_list_for_display)
knife.run
end
+
+ describe "with options with_uri argument" do
+ before do
+ knife.config[:with_uri] = true
+ end
+
+ it "lists all users including hidden users" do
+ expect(knife.ui).to receive(:output).with(users)
+ knife.run
+ end
+ end
end
diff --git a/spec/unit/knife/user_password_spec.rb b/spec/unit/knife/user_password_spec.rb
new file mode 100644
index 0000000000..098597a14c
--- /dev/null
+++ b/spec/unit/knife/user_password_spec.rb
@@ -0,0 +1,64 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# Copyright:: Copyright 2014-2016 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/org"
+
+Chef::Knife::UserDelete.load_deps
+
+describe Chef::Knife::UserPassword do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @knife = Chef::Knife::UserPassword.new
+ @user_name = "foobar"
+ @password = "abc123"
+ @user = double("Chef::User")
+ allow(@user).to receive(:root_rest).and_return(root_rest)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ end
+
+ describe "should change user's password" do
+ before :each do
+ @knife.name_args << @user_name << @password
+ end
+
+ it "with username and password" do
+ result = { "password" => [], "recovery_authentication_enabled" => true }
+ allow(@user).to receive(:[]).with("organization")
+
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(@user.root_rest).to receive(:get).with("users/#{@user_name}").and_return(result)
+ expect(@user.root_rest).to receive(:put).with("users/#{@user_name}", result)
+ expect(@knife.ui).to receive(:msg).with("Authentication info updated for #{@user_name}.")
+
+ @knife.run
+ end
+ end
+
+ describe "should not change user's password" do
+
+ it "ails with an informative message" do
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal).with("You must pass two arguments")
+ expect(@knife.ui).to receive(:fatal).with("Note that --enable-external-auth cannot be passed with a password")
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+end
diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb
index 198b9352f3..30742d8c21 100644
--- a/spec/unit/knife/user_show_spec.rb
+++ b/spec/unit/knife/user_show_spec.rb
@@ -17,30 +17,75 @@
#
require "spec_helper"
+require "chef/org"
+
+Chef::Knife::UserShow.load_deps
describe Chef::Knife::UserShow do
let(:knife) { Chef::Knife::UserShow.new }
let(:user_mock) { double("user_mock") }
- let(:stdout) { StringIO.new }
-
- before do
- Chef::Knife::UserShow.load_deps
- knife.name_args = [ "my_user" ]
- allow(user_mock).to receive(:username).and_return("my_user")
- allow(knife.ui).to receive(:stderr).and_return(stdout)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @user_name = "foobar"
+ @password = "abc123"
+ @user = double("Chef::User")
+ allow(@user).to receive(:root_rest).and_return(root_rest)
+ # allow(Chef::User).to receive(:new).and_return(@user)
+ @key = "You don't come into cooking to get rich - Ramsay"
end
- it "loads and displays the user" do
- expect(Chef::UserV1).to receive(:load).with("my_user").and_return(user_mock)
- expect(knife).to receive(:format_for_display).with(user_mock)
- knife.run
+ describe "withot organisation argument" do
+ before do
+ knife.name_args = [ "my_user" ]
+ allow(user_mock).to receive(:username).and_return("my_user")
+ end
+
+ it "should load the user" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(@user.root_rest).to receive(:get).with("users/my_user")
+ knife.run
+ end
+
+ it "loads and displays the user" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(@user.root_rest).to receive(:get).with("users/my_user")
+ expect(knife).to receive(:format_for_display)
+ knife.run
+ end
+
+ it "prints usage and exits when a user name is not provided" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
end
- it "prints usage and exits when a user name is not provided" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
+ describe "with organisation argument" do
+ before :each do
+ @user_name = "foobar"
+ @org_name = "abc_org"
+ knife.name_args << @user_name << @org_name
+ @org = double("Chef::Org")
+ allow(Chef::Org).to receive(:new).and_return(@org)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ end
+
+ let(:orgs) do
+ [@org]
+ end
+
+ it "should load the user with organisation" do
+
+ result = { "organizations" => [] }
+ knife.config[:with_orgs] = true
+
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ allow(@org).to receive(:[]).with("organization").and_return({ "name" => "test" })
+ expect(@user.root_rest).to receive(:get).with("users/#{@user_name}").and_return(result)
+ expect(@user.root_rest).to receive(:get).with("users/#{@user_name}/organizations").and_return(orgs)
+ knife.run
+ end
end
end
diff --git a/spec/unit/org_group_spec.rb b/spec/unit/org_group_spec.rb
new file mode 100644
index 0000000000..47a2587a9b
--- /dev/null
+++ b/spec/unit/org_group_spec.rb
@@ -0,0 +1,45 @@
+
+# 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/org"
+
+describe Chef::Org do
+ let(:org) { Chef::Org.new("myorg") }
+
+ describe "API Interactions" do
+ before(:each) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ @rest = double("rest")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@rest)
+ end
+
+ describe "group" do
+ it "should load group data when it's not loaded." do
+ expect(@rest).to receive(:get_rest).with("organizations/myorg/groups/admins").and_return({})
+ org.group("admins")
+ end
+
+ it "should not load group data a second time when it's already loaded." do
+ expect(@rest).to receive(:get_rest)
+ .with("organizations/myorg/groups/admins")
+ .and_return({ anything: "goes" })
+ .exactly(:once)
+ admin1 = org.group("admins")
+ admin2 = org.group("admins")
+ expect(admin1).to eq admin2
+ end
+ end
+ end
+end