summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsnehaldwivedi <sdwivedi@msystechnologies.com>2020-07-22 01:57:39 -0700
committersnehaldwivedi <sdwivedi@msystechnologies.com>2021-02-16 02:45:12 -0800
commit1f655be8219c9e20dffd68adc2ff97c29e2c29b3 (patch)
tree61989f31856029fea9534cc99cc7473bdd5454e5
parent3f4ef1b91f7a24d2c533eb4791ae099f8de49f4f (diff)
downloadchef-1f655be8219c9e20dffd68adc2ff97c29e2c29b3.tar.gz
Merge this knife plugin into Chef directly
Signed-off-by: snehaldwivedi <sdwivedi@msystechnologies.com>
-rw-r--r--lib/chef/knife/opc_org_create.rb67
-rw-r--r--lib/chef/knife/opc_org_delete.rb33
-rw-r--r--lib/chef/knife/opc_org_edit.rb49
-rw-r--r--lib/chef/knife/opc_org_list.rb45
-rw-r--r--lib/chef/knife/opc_org_show.rb32
-rw-r--r--lib/chef/knife/opc_org_user_add.rb61
-rw-r--r--lib/chef/knife/opc_org_user_remove.rb103
-rw-r--r--lib/chef/knife/opc_user_create.rb101
-rw-r--r--lib/chef/knife/opc_user_delete.rb144
-rw-r--r--lib/chef/knife/opc_user_edit.rb69
-rw-r--r--lib/chef/knife/opc_user_list.rb37
-rw-r--r--lib/chef/knife/opc_user_password.rb72
-rw-r--r--lib/chef/knife/opc_user_show.rb41
-rw-r--r--lib/chef/mixin/root_rest.rb31
-rw-r--r--lib/chef/org/group_operations.rb60
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/unit/knife/opc_org_create_spec.rb61
-rw-r--r--spec/unit/knife/opc_org_delete_spec.rb42
-rw-r--r--spec/unit/knife/opc_org_list_spec.rb39
-rw-r--r--spec/unit/knife/opc_org_show_spec.rb48
-rw-r--r--spec/unit/knife/opc_org_user_add_spec.rb24
-rw-r--r--spec/unit/knife/opc_user_create_spec.rb121
-rw-r--r--spec/unit/knife/opc_user_delete_spec.rb157
-rw-r--r--spec/unit/knife/opc_user_list_spec.rb56
-rw-r--r--spec/unit/knife/opc_user_password_spec.rb67
-rw-r--r--spec/unit/knife/opc_user_show_spec.rb68
-rw-r--r--spec/unit/org_group_spec.rb46
27 files changed, 1678 insertions, 0 deletions
diff --git a/lib/chef/knife/opc_org_create.rb b/lib/chef/knife/opc_org_create.rb
new file mode 100644
index 0000000000..4e016f2630
--- /dev/null
+++ b/lib/chef/knife/opc_org_create.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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.
+#
+
+module Opc
+ class OpcOrgCreate < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc 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"
+ require_relative "../org/group_operations"
+ 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
+ end
+ end
+end
diff --git a/lib/chef/knife/opc_org_delete.rb b/lib/chef/knife/opc_org_delete.rb
new file mode 100644
index 0000000000..ff30c4b6dc
--- /dev/null
+++ b/lib/chef/knife/opc_org_delete.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcOrgDelete < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc org delete ORG_NAME"
+
+ include Chef::Mixin::RootRestv0
+
+ 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
diff --git a/lib/chef/knife/opc_org_edit.rb b/lib/chef/knife/opc_org_edit.rb
new file mode 100644
index 0000000000..202369707d
--- /dev/null
+++ b/lib/chef/knife/opc_org_edit.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcOrgEdit < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc 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
+
+ include Chef::Mixin::RootRestv0
+
+ original_org = root_rest.get("organizations/#{org_name}")
+ edited_org = edit_data(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
diff --git a/lib/chef/knife/opc_org_list.rb b/lib/chef/knife/opc_org_list.rb
new file mode 100644
index 0000000000..2220b3e737
--- /dev/null
+++ b/lib/chef/knife/opc_org_list.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcOrgList < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc 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"
+
+ include Chef::Mixin::RootRestv0
+
+ 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
diff --git a/lib/chef/knife/opc_org_show.rb b/lib/chef/knife/opc_org_show.rb
new file mode 100644
index 0000000000..ee9c0c6bb3
--- /dev/null
+++ b/lib/chef/knife/opc_org_show.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcOrgShow < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc org show ORGNAME"
+
+ include Chef::Mixin::RootRestv0
+
+ def run
+ org_name = @name_args[0]
+ ui.output root_rest.get("organizations/#{org_name}")
+ end
+ end
+end
diff --git a/lib/chef/knife/opc_org_user_add.rb b/lib/chef/knife/opc_org_user_add.rb
new file mode 100644
index 0000000000..2209fdd4f9
--- /dev/null
+++ b/lib/chef/knife/opc_org_user_add.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Marc Paradise (<marc@chef.io>)
+# 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.
+#
+
+module Opc
+ class OpcOrgUserAdd < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc 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"
+ require_relative "../org/group_operations"
+ 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
diff --git a/lib/chef/knife/opc_org_user_remove.rb b/lib/chef/knife/opc_org_user_remove.rb
new file mode 100644
index 0000000000..fd3c518e9c
--- /dev/null
+++ b/lib/chef/knife/opc_org_user_remove.rb
@@ -0,0 +1,103 @@
+#
+# Author:: Marc Paradise (<marc@getchef.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.
+#
+
+module Opc
+ class OpcOrgUserRemove < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc 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_relative "../org/group_operations"
+ 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
diff --git a/lib/chef/knife/opc_user_create.rb b/lib/chef/knife/opc_user_create.rb
new file mode 100644
index 0000000000..48fd4eba8f
--- /dev/null
+++ b/lib/chef/knife/opc_user_create.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcUserCreate < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc user create USERNAME FIRST_NAME [MIDDLE_NAME] LAST_NAME EMAIL PASSWORD"
+
+ option :filename,
+ long: "--filename FILENAME",
+ short: "-f FILENAME",
+ description: "Write private key to FILENAME rather than STDOUT"
+
+ 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"
+
+ include Chef::Mixin::RootRestv0
+
+ def run
+ case @name_args.count
+ when 6
+ username, first_name, middle_name, last_name, email, password = @name_args
+ when 5
+ username, first_name, last_name, email, password = @name_args
+ when 4
+ username, first_name, last_name, email = @name_args
+ else
+ ui.fatal "Wrong number of arguments"
+ show_usage
+ exit 1
+ end
+ password = prompt_for_password if config[:passwordprompt]
+ unless password
+ ui.fatal "You must either provide a password or use the --prompt-for-password (-p) option"
+ exit 1
+ end
+ middle_name ||= ""
+
+ user_hash = {
+ username: username,
+ first_name: first_name,
+ middle_name: middle_name,
+ last_name: last_name,
+ display_name: "#{first_name} #{last_name}",
+ email: email,
+ password: password,
+ }
+
+ # Check the file before creating the user so the api is more transactional.
+ if config[:filename]
+ file = config[:filename]
+ unless File.exist?(file) ? File.writable?(file) : File.writable?(File.dirname(file))
+ ui.fatal "File #{config[:filename]} is not writable. Check permissions."
+ exit 1
+ end
+ end
+
+ result = root_rest.post("users/", user_hash)
+ if config[:filename]
+ File.open(config[:filename], "w") do |f|
+ f.print(result["private_key"])
+ end
+ else
+ ui.msg result["private_key"]
+ end
+ if config[:orgname]
+ request_body = { user: username }
+ response = root_rest.post("organizations/#{config[:orgname]}/association_requests", request_body)
+ association_id = response["uri"].split("/").last
+ root_rest.put("users/#{username}/association_requests/#{association_id}", { response: "accept" })
+ end
+ end
+
+ def prompt_for_password
+ ui.ask("Please enter the user's password: ") { |q| q.echo = false }
+ end
+ end
+end
diff --git a/lib/chef/knife/opc_user_delete.rb b/lib/chef/knife/opc_user_delete.rb
new file mode 100644
index 0000000000..b749b8a5d4
--- /dev/null
+++ b/lib/chef/knife/opc_user_delete.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcUserDelete < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc user delete USERNAME [-d] [-R]"
+
+ 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
+ include Chef::Mixin::RootRestv0
+
+ deps do
+ require_relative "../org"
+ require_relative "../org/group_operations"
+ end
+
+ def run
+ @username = @name_args[0]
+ admin_memberships = []
+ unremovable_memberships = []
+
+ 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/opc_user_edit.rb b/lib/chef/knife/opc_user_edit.rb
new file mode 100644
index 0000000000..26118bce9f
--- /dev/null
+++ b/lib/chef/knife/opc_user_edit.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcUserEdit < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc user edit USERNAME"
+
+ 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"
+
+ include Chef::Mixin::RootRestv0
+
+ def run
+ user_name = @name_args[0]
+
+ if user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ original_user = root_rest.get("users/#{user_name}")
+ if config[:input]
+ edited_user = JSON.parse(IO.read(config[:input]))
+ else
+ edited_user = edit_data(original_user)
+ end
+ if original_user != edited_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
+end
diff --git a/lib/chef/knife/opc_user_list.rb b/lib/chef/knife/opc_user_list.rb
new file mode 100644
index 0000000000..33402175ec
--- /dev/null
+++ b/lib/chef/knife/opc_user_list.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcUserList < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc user list"
+
+ option :with_uri,
+ long: "--with-uri",
+ short: "-w",
+ description: "Show corresponding URIs"
+
+ include Chef::Mixin::RootRestv0
+
+ def run
+ results = root_rest.get("users")
+ ui.output(ui.format_list_for_display(results))
+ end
+ end
+end
diff --git a/lib/chef/knife/opc_user_password.rb b/lib/chef/knife/opc_user_password.rb
new file mode 100644
index 0000000000..b9b087324e
--- /dev/null
+++ b/lib/chef/knife/opc_user_password.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Tyler Cloke (<tyler@getchef.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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcUserPassword < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc 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)"
+
+ include Chef::Mixin::RootRestv0
+
+ 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.
+ user = root_rest.get("users/#{user_name}")
+
+ user["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).
+ user["recovery_authentication_enabled"] = !config[:enable_external_auth]
+
+ begin
+ root_rest.put("users/#{user_name}", user)
+ rescue => e
+ raise e
+ end
+
+ ui.msg("Authentication info updated for #{user_name}.")
+ end
+ end
+end
diff --git a/lib/chef/knife/opc_user_show.rb b/lib/chef/knife/opc_user_show.rb
new file mode 100644
index 0000000000..26b3b04474
--- /dev/null
+++ b/lib/chef/knife/opc_user_show.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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_relative "../mixin/root_rest"
+
+module Opc
+ class OpcUserShow < Chef::Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife opc user show USERNAME"
+
+ option :with_orgs,
+ long: "--with-orgs",
+ short: "-l"
+
+ include Chef::Mixin::RootRestv0
+
+ def run
+ user_name = @name_args[0]
+ 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
+ ui.output results
+ end
+ end
+end
diff --git a/lib/chef/mixin/root_rest.rb b/lib/chef/mixin/root_rest.rb
new file mode 100644
index 0000000000..967213a16c
--- /dev/null
+++ b/lib/chef/mixin/root_rest.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright 2011-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 "chef/server_api"
+class Chef
+ module Mixin
+ module RootRestv0
+ def root_rest
+ # Use v0 API for now
+ # Rather than upgrade all of this code to move to v1, the goal is to remove the
+ # need for this plugin. See
+ # https://github.com/chef/chef/issues/3517
+ @root_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], { api_version: "0" })
+ end
+ end
+ end
+end
diff --git a/lib/chef/org/group_operations.rb b/lib/chef/org/group_operations.rb
new file mode 100644
index 0000000000..fd75e50f41
--- /dev/null
+++ b/lib/chef/org/group_operations.rb
@@ -0,0 +1,60 @@
+require_relative "../org"
+
+class Chef
+ class Org
+ module GroupOperations
+ 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
+ include Chef::Org::GroupOperations
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 5d49d54cd3..c1507e3203 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -331,5 +331,9 @@ module WEBrick
end
end
+class Chef::Knife
+ include Opc
+end
+
# Enough stuff needs json serialization that I'm just adding it here for equality asserts
require "chef/json_compat"
diff --git a/spec/unit/knife/opc_org_create_spec.rb b/spec/unit/knife/opc_org_create_spec.rb
new file mode 100644
index 0000000000..5d61b5ed2b
--- /dev/null
+++ b/spec/unit/knife/opc_org_create_spec.rb
@@ -0,0 +1,61 @@
+require "spec_helper"
+require "chef/org"
+require "chef/org/group_operations"
+require "chef/knife/opc_org_create"
+
+describe Opc::OpcOrgCreate do
+ before :each do
+ Chef::Knife::OpcOrgCreate.load_deps
+ @knife = Chef::Knife::OpcOrgCreate.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/opc_org_delete_spec.rb b/spec/unit/knife/opc_org_delete_spec.rb
new file mode 100644
index 0000000000..bac082790b
--- /dev/null
+++ b/spec/unit/knife/opc_org_delete_spec.rb
@@ -0,0 +1,42 @@
+#
+# 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::OpcOrgDelete do
+
+ before :each do
+ @knife = Chef::Knife::OpcOrgDelete.new
+ @org_name = "foobar"
+ @knife.name_args << @org_name
+ end
+
+ let(:rest) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ root_rest = double("rest")
+ allow(Chef::ServerAPI).to receive(:new).and_return(root_rest)
+ end
+
+ it "should confirm that you want to delete and then delete organizations" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(@knife.ui).to receive(:confirm).with("Do you want to delete the organization #{@org_name}")
+ expect(rest).to receive(:delete).with("organizations/#{@org_name}")
+ expect(@knife.ui).to receive(:output)
+ @knife.run
+ end
+end
diff --git a/spec/unit/knife/opc_org_list_spec.rb b/spec/unit/knife/opc_org_list_spec.rb
new file mode 100644
index 0000000000..00401f1c3c
--- /dev/null
+++ b/spec/unit/knife/opc_org_list_spec.rb
@@ -0,0 +1,39 @@
+require "spec_helper"
+require "chef/knife/opc_org_list"
+
+describe Opc::OpcOrgList do
+
+ let(:orgs) do
+ {
+ "org1" => "first",
+ "org2" => "second",
+ "hiddenhiddenhiddenhi" => "hidden",
+ }
+ end
+
+ before :each do
+ @rest = double("Chef::ServerAPI")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@rest)
+ allow(@rest).to receive(:get).with("organizations").and_return(orgs)
+ @knife = Chef::Knife::OpcOrgList.new
+ 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/opc_org_show_spec.rb b/spec/unit/knife/opc_org_show_spec.rb
new file mode 100644
index 0000000000..7561119b21
--- /dev/null
+++ b/spec/unit/knife/opc_org_show_spec.rb
@@ -0,0 +1,48 @@
+#
+# 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::OpcOrgShow do
+
+ before :each do
+ @knife = Chef::Knife::OpcOrgShow.new
+ @org_name = "foobar"
+ @knife.name_args << @org_name
+ end
+
+ let(:rest) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ root_rest = double("rest")
+ allow(Chef::ServerAPI).to receive(:new).and_return(root_rest)
+ end
+
+ it "should load the organisation" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(rest).to receive(:get).with("organizations/#{@org_name}")
+ @knife.run
+ end
+
+ it "should pretty print the output organisation" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(rest).to receive(:get).with("organizations/#{@org_name}")
+ expect(@knife.ui).to receive(:output)
+ @knife.run
+ end
+end
diff --git a/spec/unit/knife/opc_org_user_add_spec.rb b/spec/unit/knife/opc_org_user_add_spec.rb
new file mode 100644
index 0000000000..37ac4274e3
--- /dev/null
+++ b/spec/unit/knife/opc_org_user_add_spec.rb
@@ -0,0 +1,24 @@
+require "spec_helper"
+require "chef/org"
+require "chef/org/group_operations"
+require "chef/knife/opc_org_user_add"
+
+describe Opc::OpcOrgUserAdd do
+ context "with --admin" do
+ subject(:knife) { Chef::Knife::OpcOrgUserAdd.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/opc_user_create_spec.rb b/spec/unit/knife/opc_user_create_spec.rb
new file mode 100644
index 0000000000..e614015c3c
--- /dev/null
+++ b/spec/unit/knife/opc_user_create_spec.rb
@@ -0,0 +1,121 @@
+#
+# 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 Opc::OpcUserCreate do
+
+ before :each do
+ @knife = Chef::Knife::OpcUserCreate.new
+ @user_name = "foobar"
+ @first_name = "foo"
+ @last_name = "bar"
+ @email = "abc@test.com"
+ @password = "t123"
+ @knife.config[:yes] = true
+ end
+
+ let(:rest) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ root_rest = double("rest")
+ allow(Chef::ServerAPI).to receive(:new).and_return(root_rest)
+ end
+
+ describe "with no user_name and user_fullname" do
+
+ before :each do
+ @knife.config[:orgname] = "ramsay"
+ end
+
+ it "fails with an informative message" do
+ expect(@knife.ui).to receive(:fatal).with("Wrong number of arguments")
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with user_name, first_name, last_name, email and password" do
+ before :each do
+ @user = double("Chef::User")
+ allow(Chef::User).to receive(:new).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 << @user_name << @first_name << @last_name << @email << @password
+ end
+
+ it "creates an user" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(rest).to receive(:post).and_return(@user)
+ expect(@knife.ui).to receive(:msg).with(@key)
+ @knife.run
+ end
+
+ 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
+
+ let(:request_body) {
+ { user: @user_name }
+ }
+
+ it "creates an user, associates a user, and adds it to the admins group" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(rest).to receive(:post).and_return(@user)
+ expect(rest).to receive(:post).with("organizations/ramsay/association_requests", request_body).and_return(@user)
+ expect(rest).to receive(:put).with("users/foobar/association_requests/1", { response: "accept" })
+ @knife.run
+ end
+ end
+ end
+
+ describe "with prompt password" do
+ before :each do
+ @user = double("Chef::User")
+ allow(Chef::User).to receive(:new).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.config[:passwordprompt] = true
+ @knife.name_args << @user_name << @first_name << @last_name << @email
+ end
+
+ it "creates an user" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(rest).to receive(:post).and_return(@user)
+ expect(@knife.ui).to receive(:msg).with(@key)
+ @knife.run
+ end
+ end
+
+ describe "should raise prompt password error" do
+ before :each do
+ @user = double("Chef::User")
+ allow(Chef::User).to receive(:new).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 << @user_name << @first_name << @last_name << @email
+ end
+
+ it "fails with an informative message" do
+ expect(@knife.ui).to receive(:fatal).with("You must either provide a password or use the --prompt-for-password (-p) option")
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+end
diff --git a/spec/unit/knife/opc_user_delete_spec.rb b/spec/unit/knife/opc_user_delete_spec.rb
new file mode 100644
index 0000000000..7eddbc7a99
--- /dev/null
+++ b/spec/unit/knife/opc_user_delete_spec.rb
@@ -0,0 +1,157 @@
+require "spec_helper"
+require "chef/org"
+require "chef/org/group_operations"
+require "chef/knife/opc_user_delete"
+
+describe Opc::OpcUserDelete do
+ subject(:knife) { Chef::Knife::OpcUserDelete.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::OpcUserDelete.new }
+
+ let(:orgs_data) do
+ [{ "organization" => { "name" => "non-admin-member" } },
+ { "organization" => { "name" => "solo-admin-member" } },
+ { "organization" => { "name" => "shared-admin-member" } },
+ ]
+ end
+
+ before(:each) do
+ 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
+ context "without --remove-from-admin-groups" do
+
+ 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
+
+ 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
+
+ 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/opc_user_list_spec.rb b/spec/unit/knife/opc_user_list_spec.rb
new file mode 100644
index 0000000000..f6f2d1a89a
--- /dev/null
+++ b/spec/unit/knife/opc_user_list_spec.rb
@@ -0,0 +1,56 @@
+#
+# 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/knife/opc_user_list"
+
+describe Opc::OpcUserList do
+
+ let(:users) do
+ {
+ "user1" => "first",
+ "user2" => "second",
+ }
+ 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)
+ @knife = Chef::Knife::OpcUserList.new
+ 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
+
+ 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
+end
diff --git a/spec/unit/knife/opc_user_password_spec.rb b/spec/unit/knife/opc_user_password_spec.rb
new file mode 100644
index 0000000000..0ca7394f93
--- /dev/null
+++ b/spec/unit/knife/opc_user_password_spec.rb
@@ -0,0 +1,67 @@
+#
+# 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::OpcUserPassword do
+
+ before :each do
+ @knife = Chef::Knife::OpcUserPassword.new
+ @user_name = "foobar"
+ @password = "abc123"
+ @user = double("Chef::User")
+ allow(Chef::User).to receive(:new).and_return(@user)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ end
+
+ let(:rest) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ root_rest = double("rest")
+ allow(Chef::ServerAPI).to receive(:new).and_return(root_rest)
+ 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(@knife).to receive(:root_rest).and_return(rest)
+ allow(@user).to receive(:[]).with("organization")
+
+ expect(rest).to receive(:get).with("users/#{@user_name}").and_return(result)
+ expect(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/opc_user_show_spec.rb b/spec/unit/knife/opc_user_show_spec.rb
new file mode 100644
index 0000000000..224d801cb2
--- /dev/null
+++ b/spec/unit/knife/opc_user_show_spec.rb
@@ -0,0 +1,68 @@
+#
+# 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::OpcUserShow do
+
+ before :each do
+ @knife = Chef::Knife::OpcUserShow.new
+ @user_name = "foobar"
+ @knife.name_args << @user_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(:rest) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ root_rest = double("rest")
+ allow(Chef::ServerAPI).to receive(:new).and_return(root_rest)
+ end
+
+ let(:orgs) do
+ [@org]
+ end
+
+ it "should load the user" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(rest).to receive(:get).with("users/#{@user_name}")
+ @knife.run
+ end
+
+ it "should pretty print the output user" do
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ expect(rest).to receive(:get).with("users/#{@user_name}")
+ expect(@knife.ui).to receive(:output)
+ @knife.run
+ end
+
+ it "should load the user with organisation" do
+ @org_name = "abc_org"
+ @knife.name_args << @user_name << @org_name
+ result = { "organizations" => [] }
+ @knife.config[:with_orgs] = true
+
+ allow(@knife).to receive(:root_rest).and_return(rest)
+ allow(@org).to receive(:[]).with("organization").and_return({ "name" => "test" })
+ expect(rest).to receive(:get).with("users/#{@user_name}").and_return(result)
+ expect(rest).to receive(:get).with("users/#{@user_name}/organizations").and_return(orgs)
+ @knife.run
+ end
+end
diff --git a/spec/unit/org_group_spec.rb b/spec/unit/org_group_spec.rb
new file mode 100644
index 0000000000..0d72228f22
--- /dev/null
+++ b/spec/unit/org_group_spec.rb
@@ -0,0 +1,46 @@
+
+# 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"
+require "chef/org/group_operations"
+
+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