diff options
author | snehaldwivedi <sdwivedi@msystechnologies.com> | 2020-07-31 04:37:07 -0700 |
---|---|---|
committer | snehaldwivedi <sdwivedi@msystechnologies.com> | 2021-02-16 02:45:12 -0800 |
commit | f9f3fe38013846ffc8a7bb080363c27e02837307 (patch) | |
tree | 0562ea254790ac9e8c50f5375e69602c3d9580ec | |
parent | 1f655be8219c9e20dffd68adc2ff97c29e2c29b3 (diff) | |
download | chef-f9f3fe38013846ffc8a7bb080363c27e02837307.tar.gz |
Updated reviewed changes
Signed-off-by: snehaldwivedi <sdwivedi@msystechnologies.com>
45 files changed, 1085 insertions, 1300 deletions
diff --git a/lib/chef/group.rb b/lib/chef/group.rb new file mode 100644 index 0000000000..d1b4f1ac01 --- /dev/null +++ b/lib/chef/group.rb @@ -0,0 +1,75 @@ +# +# 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 "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/opc_org_create.rb b/lib/chef/knife/opc_org_create.rb deleted file mode 100644 index 4e016f2630..0000000000 --- a/lib/chef/knife/opc_org_create.rb +++ /dev/null @@ -1,67 +0,0 @@ -# -# 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_list.rb b/lib/chef/knife/opc_org_list.rb deleted file mode 100644 index 2220b3e737..0000000000 --- a/lib/chef/knife/opc_org_list.rb +++ /dev/null @@ -1,45 +0,0 @@ -# -# 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_user_add.rb b/lib/chef/knife/opc_org_user_add.rb deleted file mode 100644 index 2209fdd4f9..0000000000 --- a/lib/chef/knife/opc_org_user_add.rb +++ /dev/null @@ -1,61 +0,0 @@ -# -# 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 deleted file mode 100644 index fd3c518e9c..0000000000 --- a/lib/chef/knife/opc_org_user_remove.rb +++ /dev/null @@ -1,103 +0,0 @@ -# -# 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 deleted file mode 100644 index 48fd4eba8f..0000000000 --- a/lib/chef/knife/opc_user_create.rb +++ /dev/null @@ -1,101 +0,0 @@ -# -# 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 deleted file mode 100644 index b749b8a5d4..0000000000 --- a/lib/chef/knife/opc_user_delete.rb +++ /dev/null @@ -1,144 +0,0 @@ -# -# 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 deleted file mode 100644 index 26118bce9f..0000000000 --- a/lib/chef/knife/opc_user_edit.rb +++ /dev/null @@ -1,69 +0,0 @@ -# -# 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 deleted file mode 100644 index 33402175ec..0000000000 --- a/lib/chef/knife/opc_user_list.rb +++ /dev/null @@ -1,37 +0,0 @@ -# -# 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 deleted file mode 100644 index b9b087324e..0000000000 --- a/lib/chef/knife/opc_user_password.rb +++ /dev/null @@ -1,72 +0,0 @@ -# -# 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/org_create.rb b/lib/chef/knife/org_create.rb new file mode 100644 index 0000000000..0eba7ccda6 --- /dev/null +++ b/lib/chef/knife/org_create.rb @@ -0,0 +1,68 @@ +# +# 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. +# + +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 + end + end + end +end diff --git a/lib/chef/knife/opc_org_delete.rb b/lib/chef/knife/org_delete.rb index ff30c4b6dc..d6748f7b21 100644 --- a/lib/chef/knife/opc_org_delete.rb +++ b/lib/chef/knife/org_delete.rb @@ -17,17 +17,19 @@ # require_relative "../mixin/root_rest" -module Opc - class OpcOrgDelete < Chef::Knife - category "CHEF ORGANIZATION MANAGEMENT" - banner "knife opc org delete ORG_NAME" +class Chef + class Knife + class OrgDelete < Knife + category "CHEF ORGANIZATION MANAGEMENT" + banner "knife org delete ORG_NAME" - include Chef::Mixin::RootRestv0 + 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}") + 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/opc_user_show.rb b/lib/chef/knife/org_edit.rb index 26b3b04474..54bbaaede6 100644 --- a/lib/chef/knife/opc_user_show.rb +++ b/lib/chef/knife/org_edit.rb @@ -17,25 +17,35 @@ # require_relative "../mixin/root_rest" -module Opc - class OpcUserShow < Chef::Knife - category "CHEF ORGANIZATION MANAGEMENT" - banner "knife opc user show USERNAME" +class Chef + class Knife + class OrgEdit < Knife + category "CHEF ORGANIZATION MANAGEMENT" + banner "knife org edit ORG" - option :with_orgs, - long: "--with-orgs", - short: "-l" + def run + org_name = @name_args[0] - include Chef::Mixin::RootRestv0 + if org_name.nil? + show_usage + ui.fatal("You must specify an organization name") + exit 1 + end - 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"] } + 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 - ui.output results end end end diff --git a/lib/chef/knife/opc_org_edit.rb b/lib/chef/knife/org_list.rb index 202369707d..1138743abe 100644 --- a/lib/chef/knife/opc_org_edit.rb +++ b/lib/chef/knife/org_list.rb @@ -17,33 +17,31 @@ # require_relative "../mixin/root_rest" -module Opc - class OpcOrgEdit < Chef::Knife - category "CHEF ORGANIZATION MANAGEMENT" - banner "knife opc org edit ORG" +class Chef + class Knife + class OrgList < Knife + category "CHEF ORGANIZATION MANAGEMENT" + banner "knife org list" - def run - org_name = @name_args[0] + option :with_uri, + long: "--with-uri", + short: "-w", + description: "Show corresponding URIs" - if org_name.nil? - show_usage - ui.fatal("You must specify an organization name") - exit 1 - end + option :all_orgs, + long: "--all-orgs", + short: "-a", + description: "Show auto-generated hidden orgs in output" 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 + 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 - - 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_show.rb b/lib/chef/knife/org_show.rb index ee9c0c6bb3..035658ad47 100644 --- a/lib/chef/knife/opc_org_show.rb +++ b/lib/chef/knife/org_show.rb @@ -17,16 +17,18 @@ # require_relative "../mixin/root_rest" -module Opc - class OpcOrgShow < Chef::Knife - category "CHEF ORGANIZATION MANAGEMENT" - banner "knife opc org show ORGNAME" +class Chef + class Knife + class OrgShow < Knife + category "CHEF ORGANIZATION MANAGEMENT" + banner "knife org show ORGNAME" - include Chef::Mixin::RootRestv0 + include Chef::Mixin::RootRestv0 - def run - org_name = @name_args[0] - ui.output root_rest.get("organizations/#{org_name}") + 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..550bb77f4d --- /dev/null +++ b/lib/chef/knife/org_user_add.rb @@ -0,0 +1,62 @@ +# +# 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. +# + +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..f9fa49611c --- /dev/null +++ b/lib/chef/knife/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. +# + +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..0517e43e49 100644 --- a/lib/chef/knife/user_create.rb +++ b/lib/chef/knife/user_create.rb @@ -1,7 +1,7 @@ # # Author:: Steven Danna (<steve@chef.io>) # Author:: Tyler Cloke (<tyler@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,12 +17,15 @@ # limitations under the License. # +require_relative "../mixin/root_rest" require_relative "../knife" require "chef-utils/dist" unless defined?(ChefUtils::Dist) class Chef class Knife - class UserCreate < Knife + class UserCreate < Chef::Knife + + include Chef::Mixin::RootRestv0 attr_accessor :user_field @@ -45,6 +48,16 @@ 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 @@ -71,8 +84,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 +104,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 + 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: ") { |q| q.echo = false } + end end end end diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb index 87c1f734bb..7ddc0871ae 100644 --- a/lib/chef/knife/user_delete.rb +++ b/lib/chef/knife/user_delete.rb @@ -1,6 +1,6 @@ # # Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,27 +17,136 @@ # require_relative "../knife" +require_relative "../mixin/root_rest" class Chef class Knife 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 + include Chef::Mixin::RootRestv0 + 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_dissociate.rb b/lib/chef/knife/user_dissociate.rb index 6af1559608..a69394dabd 100644 --- a/lib/chef/knife/user_dissociate.rb +++ b/lib/chef/knife/user_dissociate.rb @@ -1,6 +1,6 @@ # # Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb index ad9dfac079..3ff23bbad5 100644 --- a/lib/chef/knife/user_edit.rb +++ b/lib/chef/knife/user_edit.rb @@ -1,6 +1,6 @@ # # Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,18 @@ class Chef 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" + + include Chef::Mixin::RootRestv0 + def run @user_name = @name_args[0] @@ -40,13 +52,51 @@ class Chef original_user = Chef::UserV1.load(@user_name).to_hash edited_user = edit_hash(original_user) if original_user != edited_user - user = Chef::UserV1.from_hash(edited_user) - user.update - ui.msg("Saved #{user}.") + # user = Chef::UserV1.from_hash(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 + + 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..2a7e6a0e79 100644 --- a/lib/chef/knife/user_list.rb +++ b/lib/chef/knife/user_list.rb @@ -1,6 +1,6 @@ # # Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,16 +16,13 @@ # limitations under the License. # +require_relative "../mixin/root_rest" require_relative "../knife" class Chef class Knife class UserList < Knife - deps do - require_relative "../user_v1" - end - banner "knife user list (options)" option :with_uri, @@ -33,8 +30,11 @@ class Chef long: "--with-uri", description: "Show corresponding URIs." + include Chef::Mixin::RootRestv0 + def run - output(format_list_for_display(Chef::UserV1.list)) + results = root_rest.get("users") + output(format_list_for_display(results)) end end diff --git a/lib/chef/knife/user_password.rb b/lib/chef/knife/user_password.rb new file mode 100644 index 0000000000..7a237b9643 --- /dev/null +++ b/lib/chef/knife/user_password.rb @@ -0,0 +1,73 @@ +# +# 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" + +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)" + + 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 +end diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb index e59f969e9a..4383070e98 100644 --- a/lib/chef/knife/user_show.rb +++ b/lib/chef/knife/user_show.rb @@ -1,6 +1,6 @@ # # Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ # require_relative "../knife" +require_relative "../mixin/root_rest" class Chef class Knife @@ -24,12 +25,14 @@ 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" + + include Chef::Mixin::RootRestv0 + def run @user_name = @name_args[0] @@ -39,8 +42,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/mixin/root_rest.rb b/lib/chef/mixin/root_rest.rb index 967213a16c..02462fb3cb 100644 --- a/lib/chef/mixin/root_rest.rb +++ b/lib/chef/mixin/root_rest.rb @@ -24,7 +24,7 @@ class Chef # 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" }) + @root_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root]) end end end diff --git a/lib/chef/org.rb b/lib/chef/org.rb index e2b7c49051..4ba27d1ea0 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 2011-2016 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/org/group_operations.rb b/lib/chef/org/group_operations.rb.old index fd75e50f41..fd75e50f41 100644 --- a/lib/chef/org/group_operations.rb +++ b/lib/chef/org/group_operations.rb.old diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c1507e3203..5d49d54cd3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -331,9 +331,5 @@ 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_list_spec.rb b/spec/unit/knife/opc_org_list_spec.rb deleted file mode 100644 index 00401f1c3c..0000000000 --- a/spec/unit/knife/opc_org_list_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -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_user_add_spec.rb b/spec/unit/knife/opc_org_user_add_spec.rb deleted file mode 100644 index 37ac4274e3..0000000000 --- a/spec/unit/knife/opc_org_user_add_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index e614015c3c..0000000000 --- a/spec/unit/knife/opc_user_create_spec.rb +++ /dev/null @@ -1,121 +0,0 @@ -# -# 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 deleted file mode 100644 index 7eddbc7a99..0000000000 --- a/spec/unit/knife/opc_user_delete_spec.rb +++ /dev/null @@ -1,157 +0,0 @@ -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_show_spec.rb b/spec/unit/knife/opc_user_show_spec.rb deleted file mode 100644 index 224d801cb2..0000000000 --- a/spec/unit/knife/opc_user_show_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -# -# 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/knife/opc_org_create_spec.rb b/spec/unit/knife/org_create_spec.rb index 5d61b5ed2b..3c33817b55 100644 --- a/spec/unit/knife/opc_org_create_spec.rb +++ b/spec/unit/knife/org_create_spec.rb @@ -1,12 +1,27 @@ +# +# 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" -require "chef/org/group_operations" -require "chef/knife/opc_org_create" -describe Opc::OpcOrgCreate do +describe Chef::Knife::OrgCreate do before :each do - Chef::Knife::OpcOrgCreate.load_deps - @knife = Chef::Knife::OpcOrgCreate.new + 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" diff --git a/spec/unit/knife/opc_org_delete_spec.rb b/spec/unit/knife/org_delete_spec.rb index bac082790b..a39965c761 100644 --- a/spec/unit/knife/opc_org_delete_spec.rb +++ b/spec/unit/knife/org_delete_spec.rb @@ -1,6 +1,6 @@ # # Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2014-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,10 @@ require "spec_helper" -describe Chef::Knife::OpcOrgDelete do +describe Chef::Knife::OrgDelete do before :each do - @knife = Chef::Knife::OpcOrgDelete.new + @knife = Chef::Knife::OrgDelete.new @org_name = "foobar" @knife.name_args << @org_name end diff --git a/spec/unit/knife/opc_user_list_spec.rb b/spec/unit/knife/org_list_spec.rb index f6f2d1a89a..de4affe83c 100644 --- a/spec/unit/knife/opc_user_list_spec.rb +++ b/spec/unit/knife/org_list_spec.rb @@ -1,6 +1,5 @@ # -# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2014-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,39 +16,39 @@ # require "spec_helper" -require "chef/knife/opc_user_list" -describe Opc::OpcUserList do +describe Chef::Knife::OrgList do - let(:users) do + let(:orgs) do { - "user1" => "first", - "user2" => "second", + "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("users").and_return(users) - @knife = Chef::Knife::OpcUserList.new + allow(@rest).to receive(:get).with("organizations").and_return(orgs) + @knife = Chef::Knife::OrgList.new end describe "with no arguments" do - it "lists all non users" do - expect(@knife.ui).to receive(:output).with(%w{user1 user2}) + it "lists all non hidden orgs" do + expect(@knife.ui).to receive(:output).with(%w{org1 org2}) @knife.run end end - describe "with all_users argument" do + describe "with all_orgs argument" do before do - @knife.config[:all_users] = true + @knife.config[:all_orgs] = true end - it "lists all users including hidden users" do - expect(@knife.ui).to receive(:output).with(%w{user1 user2}) + it "lists all orgs including hidden orgs" do + expect(@knife.ui).to receive(:output).with(%w{hiddenhiddenhiddenhi org1 org2}) @knife.run end end diff --git a/spec/unit/knife/opc_org_show_spec.rb b/spec/unit/knife/org_show_spec.rb index 7561119b21..7c7e044b64 100644 --- a/spec/unit/knife/opc_org_show_spec.rb +++ b/spec/unit/knife/org_show_spec.rb @@ -1,6 +1,6 @@ # # Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,10 @@ require "spec_helper" require "chef/org" -describe Chef::Knife::OpcOrgShow do +describe Chef::Knife::OrgShow do before :each do - @knife = Chef::Knife::OpcOrgShow.new + @knife = Chef::Knife::OrgShow.new @org_name = "foobar" @knife.name_args << @org_name 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..b92ceaed21 --- /dev/null +++ b/spec/unit/knife/org_user_add_spec.rb @@ -0,0 +1,39 @@ +# +# 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::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..16479402a1 100644 --- a/spec/unit/knife/user_create_spec.rb +++ b/spec/unit/knife/user_create_spec.rb @@ -1,7 +1,7 @@ # # Author:: Steven Danna (<steve@chef.io>) # Author:: Tyler Cloke (<tyler@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,6 +38,12 @@ describe Chef::Knife::UserCreate do allow(knife.ui).to receive(:warn) end + let(:rest) do + Chef::Config[:chef_server_root] = "http://www.example.com" + root_rest = double("rest") + allow(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest) + end + context "when USERNAME isn't specified" do # from spec/support/shared/unit/knife_shared.rb it_should_behave_like "mandatory field missing" do @@ -70,19 +76,32 @@ 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") + 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 = name_args + end + + it "creates an user" do + allow(knife).to receive(:root_rest).and_return(rest) + expect(rest).to receive(:post).and_return(@user) + knife.run end end context "when all mandatory fields are validly specified" do before 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 = %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 @@ -91,13 +110,14 @@ describe Chef::Knife::UserCreate do end it "sets all the mandatory fields" do + allow(knife).to receive(:root_rest).and_return(rest) + expect(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 @@ -122,6 +142,8 @@ describe Chef::Knife::UserCreate do end it "does not set user.create_key" do + allow(knife).to receive(:root_rest).and_return(rest) + expect(rest).to receive(:post).and_return(@user) knife.run expect(knife.user.create_key).to be_falsey end @@ -129,6 +151,8 @@ describe Chef::Knife::UserCreate do context "when --prevent-keygen is not passed" do it "sets user.create_key to true" do + allow(knife).to receive(:root_rest).and_return(rest) + expect(rest).to receive(:post).and_return(@user) knife.run expect(knife.user.create_key).to be_truthy end @@ -142,6 +166,8 @@ describe Chef::Knife::UserCreate do end it "sets user.public_key" do + allow(knife).to receive(:root_rest).and_return(rest) + expect(rest).to receive(:post).and_return(@user) knife.run expect(knife.user.public_key).to eq("some_key") end @@ -149,32 +175,47 @@ describe Chef::Knife::UserCreate do context "when --user-key is not passed" do it "does not set user.public_key" do + allow(knife).to receive(:root_rest).and_return(rest) + expect(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") + 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 = name_args end - context "when --file is passed" do - before do - knife.config[:file] = "/some/path" - 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 - 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 + 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/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..31dd43cfa2 100644 --- a/spec/unit/knife/user_delete_spec.rb +++ b/spec/unit/knife/user_delete_spec.rb @@ -1,6 +1,5 @@ # -# Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# # Copyright:: Copyright 2014-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,30 +16,157 @@ # require "spec_helper" +require "chef/org" 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 + 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 - 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..c160f8c402 100644 --- a/spec/unit/knife/user_edit_spec.rb +++ b/spec/unit/knife/user_edit_spec.rb @@ -1,6 +1,6 @@ # # Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb index 21c07f3fb1..a5895cf82d 100644 --- a/spec/unit/knife/user_list_spec.rb +++ b/spec/unit/knife/user_list_spec.rb @@ -1,6 +1,6 @@ # -# Author:: Steven Danna -# Copyright:: Copyright (c) Chef Software Inc. +# 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"); @@ -19,18 +19,53 @@ require "spec_helper" 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/opc_user_password_spec.rb b/spec/unit/knife/user_password_spec.rb index 0ca7394f93..c34f2074a5 100644 --- a/spec/unit/knife/opc_user_password_spec.rb +++ b/spec/unit/knife/user_password_spec.rb @@ -1,6 +1,6 @@ # # Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2014-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,10 @@ require "spec_helper" require "chef/org" -describe Chef::Knife::OpcUserPassword do +describe Chef::Knife::UserPassword do before :each do - @knife = Chef::Knife::OpcUserPassword.new + @knife = Chef::Knife::UserPassword.new @user_name = "foobar" @password = "abc123" @user = double("Chef::User") diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb index 198b9352f3..ec473f5e7c 100644 --- a/spec/unit/knife/user_show_spec.rb +++ b/spec/unit/knife/user_show_spec.rb @@ -1,6 +1,6 @@ # # Author:: Steven Danna (<steve@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. +# Copyright:: Copyright 2011-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,30 +17,69 @@ # require "spec_helper" +require "chef/org" 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(: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 "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 + allow(knife).to receive(:root_rest).and_return(rest) + expect(rest).to receive(:get).with("users/my_user") + knife.run + end + + it "loads and displays the user" do + allow(knife).to receive(:root_rest).and_return(rest) + expect(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 + + 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 end diff --git a/spec/unit/org_group_spec.rb b/spec/unit/org_group_spec.rb index 0d72228f22..47a2587a9b 100644 --- a/spec/unit/org_group_spec.rb +++ b/spec/unit/org_group_spec.rb @@ -14,7 +14,6 @@ require "spec_helper" require "chef/org" -require "chef/org/group_operations" describe Chef::Org do let(:org) { Chef::Org.new("myorg") } |