# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ require 'rails_helper' RSpec.describe 'User endpoint', type: :request do let(:role_with_admin_user_permissions) do create(:role).tap do |role| role.permission_grant('admin.user') end end let(:admin_with_admin_user_permissions) { create(:user, roles: [role_with_admin_user_permissions]) } let(:role_without_admin_user_permissions) do create(:role).tap do |role| role.permission_grant('admin.tag') end end let(:admin_without_admin_user_permissions) { create(:user, roles: [role_without_admin_user_permissions]) } describe 'User creation' do let(:attributes) { attributes_params_for(:user) } it 'responds forbidden for customer' do requester = create(:customer) authenticated_as(requester) expect do post api_v1_users_path, params: attributes end.to not_change { User.count } expect(response).to have_http_status(:forbidden) end context 'privileged attributes' do context 'group assignment' do # group access assignment is in general only valid for agents # see HasGroups.groups_access_permission? let(:agent_attributes) do attributes.merge( roles: Role.where(name: 'Agent').map(&:name), ) end shared_examples 'group assignment' do |map_method_id| it 'responds success for admin.user' do authenticated_as(admin_with_admin_user_permissions) expect do post api_v1_users_path, params: payload end.to change(User, :count).by(1) expect(response).to have_http_status(:success) expect(User.last.send(map_method_id)).to eq(send(map_method_id)) end it 'responds forbidden for sub admin without admin.user' do authenticated_as(admin_without_admin_user_permissions) expect do post api_v1_users_path, params: payload end.to not_change { User.count } expect(response).to have_http_status(:forbidden) end it 'responds successful for agent but removes assignment' do requester = create(:agent) authenticated_as(requester) expect do post api_v1_users_path, params: payload end.to change(User, :count).by(1) expect(response).to have_http_status(:success) expect(User.last.send(map_method_id)).to be_blank end end context 'parameter groups' do let(:group_names_access_map) do Group.all.to_h { |g| [g.name, ['full']] } end let(:payload) do agent_attributes.merge( groups: group_names_access_map, ) end it_behaves_like 'group assignment', :group_names_access_map end context 'parameter group_ids' do let(:group_ids_access_map) do Group.all.to_h { |g| [g.id, ['full']] } end let(:payload) do agent_attributes.merge( group_ids: group_ids_access_map, ) end it_behaves_like 'group assignment', :group_ids_access_map end end context 'role assignment' do shared_examples 'role assignment' do let(:privileged) { Role.where(name: 'Admin') } it 'responds success for admin.user' do authenticated_as(admin_with_admin_user_permissions) expect do post api_v1_users_path, params: payload end.to change(User, :count).by(1) expect(response).to have_http_status(:success) expect(User.last.roles).to eq(privileged) end it 'responds forbidden for sub admin without admin.user' do authenticated_as(admin_without_admin_user_permissions) expect do post api_v1_users_path, params: payload end.to not_change { User.count } expect(response).to have_http_status(:forbidden) end it 'responds successful for agent but removes assignment' do requester = create(:agent) authenticated_as(requester) expect do post api_v1_users_path, params: payload end.to change(User, :count).by(1) expect(response).to have_http_status(:success) expect(User.last.roles).to eq(Role.signup_roles) end end context 'parameter roles' do let(:payload) do attributes.merge( roles: privileged.map(&:name), ) end it_behaves_like 'role assignment' end context 'parameter role_ids' do let(:payload) do attributes.merge( role_ids: privileged.map(&:id), ) end it_behaves_like 'role assignment' end end end end describe 'User update' do def authorized_update_request(requester:, requested:) authenticated_as(requester) expect do put api_v1_update_user_path(requested), params: cleaned_params_for(requested).merge(firstname: 'Changed') end.to change { requested.reload.firstname } expect(response).to have_http_status(:success) end def forbidden_update_request(requester:, requested:) authenticated_as(requester) expect do put api_v1_update_user_path(requested), params: cleaned_params_for(requested).merge(firstname: 'Changed') end.to not_change { requested.reload.attributes.tap do |attributes| # take attributes as they are for different users next if requester != requested # last_login and updated_at change every time # even if the record itself should not change attributes.delete_if do |key, _value| %w[last_login updated_at].include?(key) end end } expect(response).to have_http_status(:forbidden) end context 'request by admin.user' do let(:requester) { admin_with_admin_user_permissions } it 'is successful for same admin' do authorized_update_request( requester: requester, requested: requester, ) end it 'is successful for other admin' do authorized_update_request( requester: requester, requested: create(:admin), ) end it 'is successful for agent' do authorized_update_request( requester: requester, requested: create(:agent), ) end it 'is successful for customer' do authorized_update_request( requester: requester, requested: create(:customer), ) end end context 'request by sub admin without admin.user' do let(:requester) { admin_without_admin_user_permissions } it 'is forbidden for same admin' do forbidden_update_request( requester: requester, requested: requester, ) end it 'is forbidden for other admin' do forbidden_update_request( requester: requester, requested: create(:admin), ) end it 'is forbidden for agent' do forbidden_update_request( requester: requester, requested: create(:agent), ) end it 'is forbidden for customer' do forbidden_update_request( requester: requester, requested: create(:customer), ) end end context 'request by agent' do let(:requester) { create(:agent) } it 'is forbidden for admin' do forbidden_update_request( requester: requester, requested: create(:admin), ) end it 'is forbidden same agent' do forbidden_update_request( requester: requester, requested: requester, ) end it 'is forbidden for other agent' do forbidden_update_request( requester: requester, requested: create(:agent), ) end it 'is successful for customer' do authorized_update_request( requester: requester, requested: create(:customer), ) end end context 'request by customer' do let(:requester) { create(:customer) } it 'is forbidden for admin' do forbidden_update_request( requester: requester, requested: create(:admin), ) end it 'is forbidden for agent' do forbidden_update_request( requester: requester, requested: create(:agent), ) end it 'is forbidden for same customer' do forbidden_update_request( requester: requester, requested: requester, ) end it 'is forbidden for other customer' do forbidden_update_request( requester: requester, requested: create(:customer), ) end it 'is forbidden for same organization' do same_organization = create(:organization) requester.update!(organization: same_organization) forbidden_update_request( requester: requester, requested: create(:customer, organization: same_organization), ) end end context 'privileged attributes' do let(:requested) { create(:user) } let(:attribute) { privileged.keys.first } let(:payload) { cleaned_params_for(requested).merge(privileged) } def value_of_attribute # we need to call .to_a otherwise Rails will load the # ActiveRecord::Associations::CollectionProxy # on comparsion which is to late requested.reload.public_send(attribute).to_a end shared_examples 'admin types requests' do it 'responds success for admin.user' do authenticated_as(admin_with_admin_user_permissions) expect do put api_v1_update_user_path(requested), params: payload end.to change { value_of_attribute } expect(response).to have_http_status(:success) end it 'responds forbidden for sub admin without admin.user' do authenticated_as(admin_without_admin_user_permissions) expect do put api_v1_update_user_path(requested), params: payload end.to not_change { value_of_attribute } expect(response).to have_http_status(:forbidden) end end shared_examples 'permitted agent update' do it 'responds successful for agent but removes assignment' do requester = create(:agent) authenticated_as(requester) expect do put api_v1_update_user_path(requested), params: payload end.to change { value_of_attribute } expect(response).to have_http_status(:success) end end shared_examples 'forbidden agent update' do it 'responds successful for agent but removes assignment' do requester = create(:agent) authenticated_as(requester) expect do put api_v1_update_user_path(requested), params: payload end.to not_change { value_of_attribute } expect(response).to have_http_status(:success) end end context 'group assignment' do context 'parameter groups' do let(:privileged) do { groups: Group.all.to_h { |g| [g.name, ['full']] } } end it_behaves_like 'admin types requests' it_behaves_like 'forbidden agent update' end context 'parameter group_ids' do let(:privileged) do { group_ids: Group.all.to_h { |g| [g.id, ['full']] } } end it_behaves_like 'admin types requests' it_behaves_like 'forbidden agent update' end end context 'role assignment' do let(:admin_role) { Role.where(name: 'Admin') } context 'parameter roles' do let(:privileged) do { roles: admin_role.map(&:name), } end it_behaves_like 'admin types requests' it_behaves_like 'forbidden agent update' end context 'parameter role_ids' do let(:privileged) do { role_ids: admin_role.map(&:id), } end it_behaves_like 'admin types requests' it_behaves_like 'forbidden agent update' end end context 'organization assignment' do # We need a primary org to be able to assign secondary orgs. let(:requested) { create(:user, organization: create(:organization)) } let(:new_organizations) { create_list(:organization, 2) } context 'parameter organizations' do let(:privileged) do { organizations: new_organizations.map(&:name), } end it_behaves_like 'admin types requests' it_behaves_like 'permitted agent update' end context 'parameter organization_ids' do let(:privileged) do { organization_ids: new_organizations.map(&:id), } end it_behaves_like 'admin types requests' it_behaves_like 'permitted agent update' end end end end describe 'User deletion' do def authorized_destroy_request(requester:, requested:) authenticated_as(requester) delete api_v1_delete_user_path(requested) expect(response).to have_http_status(:success) expect(requested).not_to exist_in_database end def forbidden_destroy_request(requester:, requested:) authenticated_as(requester) delete api_v1_delete_user_path(requested) expect(response).to have_http_status(:forbidden) expect(requested).to exist_in_database end context 'request by admin.user' do let(:requester) { admin_with_admin_user_permissions } it 'is successful for same admin' do authorized_destroy_request( requester: requester, requested: requester, ) end it 'is successful for other admin' do authorized_destroy_request( requester: requester, requested: create(:admin), ) end it 'is successful for agent' do authorized_destroy_request( requester: requester, requested: create(:agent), ) end it 'is successful for customer' do authorized_destroy_request( requester: requester, requested: create(:customer), ) end end context 'request by sub admin without admin.user' do let(:requester) { admin_without_admin_user_permissions } it 'is forbidden for same admin' do forbidden_destroy_request( requester: requester, requested: requester, ) end it 'is forbidden for other admin' do forbidden_destroy_request( requester: requester, requested: create(:admin), ) end it 'is forbidden for agent' do forbidden_destroy_request( requester: requester, requested: create(:agent), ) end it 'is forbidden for customer' do forbidden_destroy_request( requester: requester, requested: create(:customer), ) end end context 'request by agent' do let(:requester) { create(:agent) } it 'is forbidden for admin' do forbidden_destroy_request( requester: requester, requested: create(:admin), ) end it 'is forbidden same agent' do forbidden_destroy_request( requester: requester, requested: requester, ) end it 'is forbidden for other agent' do forbidden_destroy_request( requester: requester, requested: create(:agent), ) end it 'is forbidden for customer' do forbidden_destroy_request( requester: requester, requested: create(:customer), ) end end context 'request by customer' do let(:requester) { create(:customer) } it 'is forbidden for admin' do forbidden_destroy_request( requester: requester, requested: create(:admin), ) end it 'is forbidden for agent' do forbidden_destroy_request( requester: requester, requested: create(:agent), ) end it 'is forbidden for same customer' do forbidden_destroy_request( requester: requester, requested: requester, ) end it 'is forbidden for other customer' do forbidden_destroy_request( requester: requester, requested: create(:customer), ) end it 'is forbidden for same organization' do same_organization = create(:organization) requester.update!(organization: same_organization) forbidden_destroy_request( requester: requester, requested: create(:customer, organization: same_organization), ) end end end end