Fixed issue #1794 - Admin users can be disabled by Agents.

This commit is contained in:
Martin Edenhofer 2018-03-28 07:33:41 +02:00
parent 46ed7a053b
commit d52634701d
28 changed files with 1782 additions and 146 deletions

View file

@ -128,7 +128,7 @@ group :development, :test do
gem 'simplecov-rcov'
# UI tests w/ Selenium
gem 'selenium-webdriver', '2.53.4'
gem 'selenium-webdriver'
# livereload on template changes (html, js, css)
gem 'guard', require: false

View file

@ -77,7 +77,7 @@ GEM
browser (2.5.2)
buftok (0.2.0)
builder (3.2.3)
childprocess (0.8.0)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
clavius (1.0.3)
clearbit (0.2.8)
@ -141,7 +141,7 @@ GEM
multipart-post (>= 1.2, < 3)
faraday-http-cache (2.0.0)
faraday (~> 0.8)
ffi (1.9.18)
ffi (1.9.23)
ffi-compiler (0.1.3)
ffi (>= 1.0.0)
rake
@ -383,10 +383,9 @@ GEM
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
selenium-webdriver (2.53.4)
selenium-webdriver (3.11.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
websocket (~> 1.0)
rubyzip (~> 1.2)
shellany (0.0.1)
simple_oauth (0.3.1)
simplecov (0.15.1)
@ -452,7 +451,6 @@ GEM
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
websocket (1.2.5)
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
@ -528,7 +526,7 @@ DEPENDENCIES
rubocop
rubyntlm!
sass-rails
selenium-webdriver (= 2.53.4)
selenium-webdriver
simplecov
simplecov-rcov
slack-notifier

View file

@ -1258,7 +1258,7 @@ class App.ObserverActionRow extends App.ObserverController
render: (object) =>
return if _.isEmpty(object)
actions = @actions()
actions = @actions(object)
@html App.view('generic/actions')(
items: actions
type: @type

View file

@ -7,14 +7,18 @@ class SidebarCustomer extends App.Controller
badgeCallback: @badgeRender
sidebarHead: 'Customer'
sidebarCallback: @showCustomer
sidebarActions: [
{
sidebarActions: []
}
if App.User.exists(@params.customer_id)
customer = App.User.find(@params.customer_id)
currentUser = App.User.find(App.Session.get('id'))
if customer.isAccessibleBy(currentUser, 'change')
@item.sidebarActions.push {
title: 'Edit Customer'
name: 'customer-edit'
callback: @editCustomer
},
]
}
@item
metaBadge: (user) =>
counter = ''

View file

@ -182,7 +182,7 @@ class App.TicketZoom extends App.Controller
@formMeta = data.form_meta
# load assets
App.Collection.loadAssets(data.assets)
App.Collection.loadAssets(data.assets, targetModel: 'Ticket')
# get data
@ticket = App.Ticket.fullLocal(@ticket_id)

View file

@ -15,6 +15,10 @@ class SidebarCustomer extends App.Controller
]
}
return @item if @ticket && @ticket.customer_id == 1
currentUser = App.User.find(App.Session.get('id'))
if @ticket.customer_id && App.User.exists(@ticket.customer_id)
customer = App.User.find(@ticket.customer_id)
if customer.isAccessibleBy(currentUser, 'change')
@item.sidebarActions.push {
title: 'Edit Customer'
name: 'customer-edit'

View file

@ -106,13 +106,10 @@ class ActionRow extends App.ObserverActionRow
newTicket: (user) =>
@navigate("ticket/create/customer/#{user.id}")
actions: =>
[
{
name: 'edit'
title: 'Edit'
callback: @editUser
}
actions: (user) =>
currentUser = App.User.find(App.Session.get('id'))
actions = [
{
name: 'history'
title: 'History'
@ -125,6 +122,15 @@ class ActionRow extends App.ObserverActionRow
}
]
if user.isAccessibleBy(currentUser, 'change')
actions.unshift {
name: 'edit'
title: 'Edit'
callback: @editUser
}
actions
class Object extends App.ObserverController
model: 'User'
observeNot:

View file

@ -308,7 +308,7 @@ set new attributes of model (remove already available attributes)
# full / load assets
if data.assets
App.Collection.loadAssets(data.assets)
App.Collection.loadAssets(data.assets, targetModel: @className)
# find / load object
else

View file

@ -285,3 +285,51 @@ class App.User extends App.Model
outOfOfficeText: ->
return @preferences.out_of_office_text if !_.isEmpty(@preferences.out_of_office_text)
App.User.outOfOfficeTextPlaceholder()
###
Checks if requester has given access level on requested.
Possible access levels are: read, update and delete
See backend method User#access?
requester = App.User.find(1)
requested = App.User.find(3)
result = requested.isAccessibleBy(requester, 'read')
returns
true|false
###
isAccessibleBy: (requester, access) ->
return true if requester.permission('admin')
capitalized = access.charAt(0).toUpperCase() + access.slice(1)
accessMethod = 'is' + capitalized + 'ableBy'
@[accessMethod](requester)
isReadableBy: (requester) ->
return true if @ownAccount(requester)
return true if requester.permission('admin.*')
return true if requester.permission('ticket.agent')
# check same organization for customers
return false if !requester.permission('ticket.customer')
@sameOrganization?(requester)
isChangeableBy: (requester) ->
return true if requester.permission('admin.user')
# allow agents to change customers
return false if !requester.permission('ticket.agent')
@permission('ticket.customer')
isDeleteableBy: (requester) ->
requester.permission('admin.user')
ownAccount: (requester) ->
@id is requester.id
sameOrganization: (requester) ->
return false if @organization_id is null
return false if requester.organization_id is null
@organization_id == requester.organization_id

View file

@ -4,35 +4,32 @@ module ChecksUserAttributesByCurrentUserPermission
private
def check_attributes_by_current_user_permission(params)
# admins can do whatever they want
return true if current_user.permissions?('admin.user')
%i[role_ids roles].each do |key|
next if !params[key]
if current_user.permissions?('ticket.agent')
params.delete(key)
else
logger.info "Role assignment is only allowed by admin! current_user_id: #{current_user.id} assigned to #{params[key].inspect}"
raise Exceptions::NotAuthorized, 'This role assignment is only allowed by admin!'
# non-agents (customers) can't set anything
response_access_deny if !current_user.permissions?('ticket.agent')
# regular agents are not allowed to set Groups and Roles
%w[Role Group].each do |model|
%w[_ids s].each do |suffix|
attribute = "#{model.downcase}#{suffix}"
values = params[attribute]
next if values.nil?
logger.warn "#{model} assignment is only allowed by admin! User with ID #{current_user.id} tried to assign #{values.inspect}"
params.delete(attribute)
end
end
if current_user.permissions?('ticket.agent') && !params[:role_ids] && !params[:roles] && params[:id].blank?
# check for create requests and set
# signup roles if no other roles are given
return true if params[:id].present?
return true if params[:role_ids]
return true if params[:roles]
params[:role_ids] = Role.signup_role_ids
true
end
%i[group_ids groups].each do |key|
next if !params[key]
if current_user.permissions?('ticket.agent')
params.delete(key)
else
logger.info "Group relation assignment is only allowed by admin! current_user_id: #{current_user.id} assigned to #{params[key].inspect}"
raise Exceptions::NotAuthorized, 'Group relation is only allowed by admin!'
end
end
return true if current_user.permissions?('ticket.agent')
response_access_deny
false
end
end

View file

@ -116,7 +116,7 @@ class UsersController < ApplicationController
# check if it's first user, the admin user
# inital admin account
count = User.all.count()
count = User.all.count
admin_account_exists = true
if count <= 2
admin_account_exists = false
@ -156,7 +156,7 @@ class UsersController < ApplicationController
Role.where(name: %w[Admin Agent]).each do |role|
role_ids.push role.id
end
Group.all().each do |group|
Group.all.each do |group|
group_ids.push group.id
end
@ -207,6 +207,7 @@ class UsersController < ApplicationController
# send inviteation if needed / only if session exists
if params[:invite].present? && current_user
sleep 5 if ENV['REMOTE_URL'].present?
token = Token.create(action: 'PasswordReset', user_id: user.id)
NotificationFactory::Mailer.notification(
template: 'user_invite',
@ -261,8 +262,6 @@ class UsersController < ApplicationController
# @response_message 200 [User] Updated User record.
# @response_message 401 Invalid session.
def update
check_attributes_by_current_user_permission(params)
user = User.find(params[:id])
access!(user, 'change')
@ -273,19 +272,11 @@ class UsersController < ApplicationController
clean_params = User.param_cleanup(clean_params, true)
user.update!(clean_params)
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:role_ids] || params[:roles])
user.associations_from_param(role_ids: params[:role_ids], roles: params[:roles])
end
# presence and permissions were checked via `check_attributes_by_current_user_permission`
privileged_attributes = params.slice(:role_ids, :roles, :group_ids, :groups, :organization_ids, :organizations)
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:group_ids] || params[:groups])
user.associations_from_param(group_ids: params[:group_ids], groups: params[:groups])
end
# only allow Admin's and Agent's
if current_user.permissions?(['admin.user', 'ticket.agent']) && (params[:organization_ids] || params[:organizations])
user.associations_from_param(organization_ids: params[:organization_ids], organizations: params[:organizations])
if privileged_attributes.present?
user.associations_from_param(privileged_attributes)
end
end
@ -1118,5 +1109,4 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
end
true
end
end

View file

@ -136,18 +136,13 @@ returns
private_class_method
def self.permission_ids_by_name(keys)
if keys.class != Array
keys = [keys]
end
permission_ids = []
keys.each do |key|
Array(keys).each_with_object([]) do |key, result|
::Permission.with_parents(key).each do |local_key|
permission = ::Permission.lookup(name: local_key)
next if !permission
permission_ids.push permission.id
result.push permission.id
end
end
permission_ids
end
private

View file

@ -13,19 +13,10 @@ class User
# #=> true
#
# @return [Boolean]
def access?(user, _access)
# check agent
return true if user.permissions?('admin.user')
return true if user.permissions?('ticket.agent')
# check customer
if user.permissions?('ticket.customer')
# access ok if its own user
return id == user.id
end
false
def access?(requester, access)
# full admins can do whatever they want
return true if requester.permissions?('admin')
send("#{access}able_by?".to_sym, requester)
end
# Checks the given access of a given user for another user and fails with an exception.
@ -42,5 +33,37 @@ class User
return if access?(user, access)
raise Exceptions::NotAuthorized
end
private
def readable_by?(requester)
return true if own_account?(requester)
return true if requester.permissions?('admin.*')
return true if requester.permissions?('ticket.agent')
# check same organization for customers
return false if !requester.permissions?('ticket.customer')
same_organization?(requester)
end
def changeable_by?(requester)
return true if requester.permissions?('admin.user')
# allow agents to change customers
return false if !requester.permissions?('ticket.agent')
permissions?('ticket.customer')
end
def deleteable_by?(requester)
requester.permissions?('admin.user')
end
def own_account?(requester)
id == requester.id
end
def same_organization?(requester)
return false if organization_id.blank?
return false if requester.organization_id.blank?
organization_id == requester.organization_id
end
end
end

View file

@ -19,6 +19,7 @@ if [ "$LEVEL" == '1' ]; then
rm test/browser/admin_overview_test.rb
rm test/browser/admin_role_test.rb
# test/browser/agent_navigation_and_title_test.rb
# test/browser/agent_organization_profile_test.rb
rm test/browser/agent_ticket_attachment_test.rb
rm test/browser/agent_ticket_auto_assignment_test.rb
rm test/browser/agent_ticket_create_reset_customer_selection_test.rb
@ -63,6 +64,7 @@ if [ "$LEVEL" == '1' ]; then
# test/browser/taskbar_session_test.rb
# test/browser/taskbar_task_test.rb
# test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
elif [ "$LEVEL" == '2' ]; then
@ -124,7 +126,8 @@ elif [ "$LEVEL" == '2' ]; then
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
#rm test/browser/user_switch_cache_test.rb
# test/browser/user_access_permissions_test.rb
# test/browser/user_switch_cache_test.rb
elif [ "$LEVEL" == '3' ]; then
echo "slicing level 3"
@ -185,6 +188,7 @@ elif [ "$LEVEL" == '3' ]; then
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
elif [ "$LEVEL" == '4' ]; then
@ -246,6 +250,7 @@ elif [ "$LEVEL" == '4' ]; then
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
elif [ "$LEVEL" == '5' ]; then
@ -261,7 +266,7 @@ elif [ "$LEVEL" == '5' ]; then
# test/browser/admin_overview_test.rb
rm test/browser/admin_role_test.rb
rm test/browser/agent_navigation_and_title_test.rb
# test/browser/agent_organization_profile_test.rb
rm test/browser/agent_organization_profile_test.rb
rm test/browser/agent_ticket_attachment_test.rb
rm test/browser/agent_ticket_auto_assignment_test.rb
rm test/browser/agent_ticket_create_reset_customer_selection_test.rb
@ -306,6 +311,7 @@ elif [ "$LEVEL" == '5' ]; then
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
elif [ "$LEVEL" == '6' ]; then
@ -369,6 +375,7 @@ elif [ "$LEVEL" == '6' ]; then
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
else

View file

@ -0,0 +1,676 @@
require 'rails_helper'
RSpec.describe UsersController, type: :controller 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 'POST #create' do
let(:attributes) { attributes_params_for(:user) }
it 'responds unauthorized for customer' do
requester = create(:customer_user)
authenticated_as(requester)
expect do
post :create, params: attributes
end.to not_change {
User.count
}
expect(response).to have_http_status(:unauthorized)
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 :create, 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 unauthorized for sub admin without admin.user' do
authenticated_as(admin_without_admin_user_permissions)
expect do
post :create, params: payload
end.to not_change {
User.count
}
expect(response).to have_http_status(:unauthorized)
end
it 'responds successful for agent but removes assignment' do
requester = create(:agent_user)
authenticated_as(requester)
expect do
post :create, 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.map { |g| [g.name, ['full']] }.to_h
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.map { |g| [g.id, ['full']] }.to_h
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 :create, 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 unauthorized for sub admin without admin.user' do
authenticated_as(admin_without_admin_user_permissions)
expect do
post :create, params: payload
end.to not_change {
User.count
}
expect(response).to have_http_status(:unauthorized)
end
it 'responds successful for agent but removes assignment' do
requester = create(:agent_user)
authenticated_as(requester)
expect do
post :create, 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 'PUT #update' do
def authorized_update_request(requester:, requested:)
authenticated_as(requester)
expect do
put :update, params: cleaned_params_for(requested).merge(firstname: 'Changed')
end.to change {
requested.reload.firstname
}
expect(response).to have_http_status(:success)
end
def unauthorized_update_request(requester:, requested:)
authenticated_as(requester)
expect do
put :update, params: cleaned_params_for(requested).merge(firstname: 'Changed')
end.to not_change {
requested.reload.attributes
}
expect(response).to have_http_status(:unauthorized)
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_user),
)
end
it 'is successful for agent' do
authorized_update_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is successful for customer' do
authorized_update_request(
requester: requester,
requested: create(:customer_user),
)
end
end
context 'request by sub admin without admin.user' do
let(:requester) { admin_without_admin_user_permissions }
it 'is unauthorized for same admin' do
unauthorized_update_request(
requester: requester,
requested: requester,
)
end
it 'is unauthorized for other admin' do
unauthorized_update_request(
requester: requester,
requested: create(:admin_user),
)
end
it 'is unauthorized for agent' do
unauthorized_update_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is unauthorized for customer' do
unauthorized_update_request(
requester: requester,
requested: create(:customer_user),
)
end
end
context 'request by agent' do
let(:requester) { create(:agent_user) }
it 'is unauthorized for admin' do
unauthorized_update_request(
requester: requester,
requested: create(:admin_user),
)
end
it 'is unauthorized same agent' do
unauthorized_update_request(
requester: requester,
requested: requester,
)
end
it 'is unauthorized for other agent' do
unauthorized_update_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is successful for customer' do
authorized_update_request(
requester: requester,
requested: create(:customer_user),
)
end
end
context 'request by customer' do
let(:requester) { create(:customer_user) }
it 'is unauthorized for admin' do
unauthorized_update_request(
requester: requester,
requested: create(:admin_user),
)
end
it 'is unauthorized for agent' do
unauthorized_update_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is unauthorized for same customer' do
unauthorized_update_request(
requester: requester,
requested: requester,
)
end
it 'is unauthorized for other customer' do
unauthorized_update_request(
requester: requester,
requested: create(:customer_user),
)
end
it 'is unauthorized for same organization' do
same_organization = create(:organization)
requester.update!(organization: same_organization)
unauthorized_update_request(
requester: requester,
requested: create(:customer_user, 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 :update, params: payload
end.to change {
value_of_attribute
}
expect(response).to have_http_status(:success)
end
it 'responds unauthorized for sub admin without admin.user' do
authenticated_as(admin_without_admin_user_permissions)
expect do
put :update, params: payload
end.to not_change {
value_of_attribute
}
expect(response).to have_http_status(:unauthorized)
end
end
shared_examples 'permitted agent update' do
it 'responds successful for agent but removes assignment' do
requester = create(:agent_user)
authenticated_as(requester)
expect do
put :update, 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_user)
authenticated_as(requester)
expect do
put :update, 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.map { |g| [g.name, ['full']] }.to_h
}
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.map { |g| [g.id, ['full']] }.to_h
}
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
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 'DELETE #destroy' do
def authorized_destroy_request(requester:, requested:)
authenticated_as(requester)
delete :destroy, params: { id: requested.id }
expect(response).to have_http_status(:success)
expect(requested).not_to exist_in_database
end
def unauthorized_destroy_request(requester:, requested:)
authenticated_as(requester)
delete :destroy, params: { id: requested.id }
expect(response).to have_http_status(:unauthorized)
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_user),
)
end
it 'is successful for agent' do
authorized_destroy_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is successful for customer' do
authorized_destroy_request(
requester: requester,
requested: create(:customer_user),
)
end
end
context 'request by sub admin without admin.user' do
let(:requester) { admin_without_admin_user_permissions }
it 'is unauthorized for same admin' do
unauthorized_destroy_request(
requester: requester,
requested: requester,
)
end
it 'is unauthorized for other admin' do
unauthorized_destroy_request(
requester: requester,
requested: create(:admin_user),
)
end
it 'is unauthorized for agent' do
unauthorized_destroy_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is unauthorized for customer' do
unauthorized_destroy_request(
requester: requester,
requested: create(:customer_user),
)
end
end
context 'request by agent' do
let(:requester) { create(:agent_user) }
it 'is unauthorized for admin' do
unauthorized_destroy_request(
requester: requester,
requested: create(:admin_user),
)
end
it 'is unauthorized same agent' do
unauthorized_destroy_request(
requester: requester,
requested: requester,
)
end
it 'is unauthorized for other agent' do
unauthorized_destroy_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is unauthorized for customer' do
unauthorized_destroy_request(
requester: requester,
requested: create(:customer_user),
)
end
end
context 'request by customer' do
let(:requester) { create(:customer_user) }
it 'is unauthorized for admin' do
unauthorized_destroy_request(
requester: requester,
requested: create(:admin_user),
)
end
it 'is unauthorized for agent' do
unauthorized_destroy_request(
requester: requester,
requested: create(:agent_user),
)
end
it 'is unauthorized for same customer' do
unauthorized_destroy_request(
requester: requester,
requested: requester,
)
end
it 'is unauthorized for other customer' do
unauthorized_destroy_request(
requester: requester,
requested: create(:customer_user),
)
end
it 'is unauthorized for same organization' do
same_organization = create(:organization)
requester.update!(organization: same_organization)
unauthorized_destroy_request(
requester: requester,
requested: create(:customer_user, organization: same_organization),
)
end
end
end
end

View file

@ -11,7 +11,7 @@ FactoryBot.define do
firstname 'Nicole'
lastname 'Braun'
email { generate(:email) }
password 'zammad'
password nil
active true
login_failed 0
updated_by_id 1

View file

@ -3,14 +3,15 @@ require 'lib/auth/backend_examples'
RSpec.describe Auth::Internal do
let(:user) { create(:user) }
let(:password) { 'zammad' }
let(:user) { create(:user, password: password) }
let(:instance) { described_class.new({ adapter: described_class.name }) }
context '#valid?' do
it_behaves_like 'Auth backend'
it 'authenticates via password' do
result = instance.valid?(user, 'zammad')
result = instance.valid?(user, password)
expect(result).to be true
end
@ -21,17 +22,16 @@ RSpec.describe Auth::Internal do
it 'converts legacy sha2 passwords' do
pw_plain = 'zammad'
sha2_pw = PasswordHash.sha2(pw_plain)
user = create(:user, password: sha2_pw)
sha2 = PasswordHash.sha2(password)
user = create(:user, password: sha2)
expect(PasswordHash.crypted?(user.password)).to be true
expect(PasswordHash.legacy?(user.password, pw_plain)).to be true
expect(PasswordHash.legacy?(user.password, password)).to be true
result = instance.valid?(user, pw_plain)
result = instance.valid?(user, password)
expect(result).to be true
expect(PasswordHash.legacy?(user.password, pw_plain)).to be false
expect(PasswordHash.legacy?(user.password, password)).to be false
expect(PasswordHash.crypted?(user.password)).to be true
end
end

View file

@ -40,8 +40,9 @@ RSpec.describe Auth do
end
it 'authenticates users' do
user = create(:user)
result = described_class.valid?(user, 'zammad')
password = 'zammad'
user = create(:user, password: password)
result = described_class.valid?(user, password)
expect(result).to be true
end
end

View file

@ -95,8 +95,9 @@ RSpec.describe User do
context '.authenticate' do
it 'authenticates by username and password' do
user = create(:user)
result = described_class.authenticate(user.login, 'zammad')
password = 'zammad'
user = create(:user, password: password)
result = described_class.authenticate(user.login, password)
expect(result).to be_an(User)
end
@ -158,7 +159,7 @@ RSpec.describe User do
end
end
context '.password_reset_via_token' do
context '#password_reset_via_token' do
it 'changes the password of the token user and destroys the token' do
token = create(:token_password_reset)
@ -191,4 +192,342 @@ RSpec.describe User do
}
end
end
context '.access?' 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]) }
context 'read' do
context 'admin' do
let(:requested) { create(:admin_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is not possible for customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'read')
expect(access).to be(false)
end
end
context 'agent' do
let(:requested) { create(:agent_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is not possible for customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'read')
expect(access).to be(false)
end
end
context 'customer' do
let(:requested) { create(:customer_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is possible for same customer' do
access = requested.access?(requested, 'read')
expect(access).to be(true)
end
it 'is possible for same organization' do
organization = create(:organization)
requester = create(:customer_user, organization: organization)
requested.update!(organization: organization)
access = requested.access?(requester, 'read')
expect(access).to be(true)
end
it 'is not possible for different customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'read')
expect(access).to be(false)
end
end
end
context 'change' do
context 'admin' do
let(:requested) { create(:admin_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'change')
expect(access).to be(true)
end
it 'is not possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
it 'is not possible for same for sub admin without admin.user' do
access = admin_without_admin_user_permissions.access?(admin_without_admin_user_permissions, 'change')
expect(access).to be(false)
end
it 'is not possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
it 'is not possible for customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
end
context 'agent' do
let(:requested) { create(:agent_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'change')
expect(access).to be(true)
end
it 'is not possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
it 'is not possible for same agent' do
access = requested.access?(requested, 'change')
expect(access).to be(false)
end
it 'is not possible for other agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
it 'is not possible for customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
end
context 'customer' do
let(:requested) { create(:customer_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'change')
expect(access).to be(true)
end
it 'is not possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
it 'is possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'change')
expect(access).to be(true)
end
it 'is not possible for same customer' do
access = requested.access?(requested, 'change')
expect(access).to be(false)
end
it 'is not possible for same organization' do
organization = create(:organization)
requester = create(:customer_user, organization: organization)
requested.update!(organization: organization)
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
it 'is not possible for different customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'change')
expect(access).to be(false)
end
end
end
context 'delete' do
context 'admin' do
let(:requested) { create(:admin_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'delete')
expect(access).to be(true)
end
it 'is not possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
it 'is not possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
it 'is not possible for customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
end
context 'agent' do
let(:requested) { create(:agent_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'delete')
expect(access).to be(true)
end
it 'is not possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
it 'is not possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
it 'is not possible for customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
end
context 'customer' do
let(:requested) { create(:customer_user) }
it 'is possible for admin.user' do
requester = admin_with_admin_user_permissions
access = requested.access?(requester, 'delete')
expect(access).to be(true)
end
it 'is not possible for sub admin without admin.user' do
requester = admin_without_admin_user_permissions
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
it 'is not possible for agent' do
requester = create(:agent_user)
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
it 'is not possible for same customer' do
access = requested.access?(requested, 'delete')
expect(access).to be(false)
end
it 'is not possible for same organization' do
organization = create(:organization)
requester = create(:customer_user, organization: organization)
requested.update!(organization: organization)
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
it 'is not possible for different customer' do
requester = create(:customer_user)
access = requested.access?(requester, 'delete')
expect(access).to be(false)
end
end
end
end
end

View file

@ -0,0 +1,26 @@
module ZammadSpecSupportAvatarCheck
def self.included(base)
# Execute in RSpec class context
base.class_exec do
# This method disables the avatar for email check for all examples.
# It's possible to re-enable the check by adding the
# meta tag `perform_avatar_for_email_check` to the needing example:
#
# @example
# it 'does stuff with avatar check', perform_avatar_for_email_check: true do
#
before(:each) do |example|
if !example.metadata[:perform_avatar_for_email_check]
allow(Avatar).to receive(:auto_detection).and_return(false)
end
end
end
end
end
RSpec.configure do |config|
config.include ZammadSpecSupportAvatarCheck
end

View file

@ -0,0 +1,89 @@
module ZammadSpecSupportController
# Authenticates all requests of the current example as the given user.
#
# @example
# authenticated_as(some_admin_user)
#
# @return nil
def authenticated_as(user)
session[:user_id] = user.id
end
# Provides a Hash of attributes for the given FactoryBot
# factory parameters which can be used as the params payload.
# Note that the attributes are "cleaned" so no created_by_id etc.
# is present.
#
# @see FactoryBot#attributes_for
#
# @example
# attributes_params_for(:admin_user, email: 'custom@example.com')
# # => {firstname: 'Nicole', email: 'custom@example.com', ...}
#
# @return [Hash{Symbol => <String, Array, Hash>}] request cleaned attributes
def attributes_params_for(*args)
filter_unused_params(attributes_for(*args))
end
# Provides a Hash of attributes for the given Model instance which can
# be used as the params payload.
# Note that the attributes are "cleaned" so no created_by_id etc.
# is present.
#
# @param [Hash] instance An ActiveRecord instance
#
# @example
# cleaned_params_for(some_admin_user)
# # => {firstname: 'Nicole', email: 'admin@example.com', ...}
#
# @return [Hash{Symbol => <String, Array, Hash>}] request cleaned attributes
def cleaned_params_for(instance)
filter_unused_params(instance.attributes)
end
# This is a self explaining internal method.
#
# @see ApplicationModel#filter_unused_params
def filter_unused_params(unfiltered)
# let's get private
ApplicationModel.send(:filter_unused_params, unfiltered)
end
def self.included(base)
# Execute in RSpec class context
base.class_exec do
# This method disables the CSRF token validation for all controller
# examples. It's possible to re-enable the check by adding the
# meta tag `verify_csrf_token` to the needing example:
#
# @example
# it 'does stuff with verified CSRF token', verify_csrf_token: true do
#
before(:each) do |example|
if !example.metadata[:verify_csrf_token]
allow(controller).to receive(:verify_csrf_token).and_return(true)
end
end
# This method disables the user device check for all controller
# examples. It's possible to re-enable the check by adding the
# meta tag `perform_user_device_check` to the needing example:
#
# @example
# it 'does stuff with user device check', perform_user_device_check: true do
#
before(:each) do |example|
if !example.metadata[:perform_user_device_check]
session[:user_device_updated_at] = Time.zone.now
end
end
end
end
end
RSpec.configure do |config|
config.include ZammadSpecSupportController, type: :controller
end

View file

@ -0,0 +1,7 @@
# taken from https://makandracards.com/makandra/1080-rspec-matcher-to-check-if-an-activerecord-exists-in-the-database
RSpec::Matchers.define :exist_in_database do
match do |actual|
actual.class.exists?(actual.id)
end
end

28
spec/support/user_info.rb Normal file
View file

@ -0,0 +1,28 @@
# This module registers a before and after each hook callback that
# resets the stored current_user_id in the UserInfo which will otherwise
# persists across multiple examples.
# This can lead to issues where actions were performed by a user created
# via a FactoryBot factory which will get removed after the example is
# completed. The UserInfo.current_user_id will persist which leads to e.g.
# DB ForeignKey violation errors.
module ZammadSpecSupportUserInfo
def self.included(base)
# Execute in RSpec class context
base.class_exec do
before(:each) do |_example|
UserInfo.current_user_id = nil
end
after(:each) do |_example|
UserInfo.current_user_id = nil
end
end
end
end
RSpec.configure do |config|
config.include ZammadSpecSupportUserInfo
end

View file

@ -38,6 +38,10 @@ class AgentTicketAttachmentTest < TestCase
#alert.accept()
#alert = alert.text
# since selenium webdriver with firefox is not able to upload files, skipp here
# https://github.com/w3c/webdriver/issues/1230
return if browser == 'firefox'
# add attachment, attachment check should quiet
file_upload(
css: '.content.active .attachmentPlaceholder-inputHolder input',
@ -112,7 +116,7 @@ class AgentTicketAttachmentTest < TestCase
)
# check content and edit screen in instance 1
match(
watch_for(
css: '.content.active div.ticket-article',
value: 'test 6 - ticket 1-1',
)

View file

@ -138,7 +138,7 @@ class ChatTest < TestCase
# init chat
click(
browser: customer,
css: '.js-chat-open',
css: '.zammad-chat .js-chat-open',
)
exists(
browser: customer,
@ -156,7 +156,7 @@ class ChatTest < TestCase
)
click(
browser: customer,
css: '.js-chat-toggle',
css: '.zammad-chat .js-chat-toggle .zammad-chat-header-icon',
)
watch_for_disappear(
browser: customer,
@ -274,7 +274,7 @@ class ChatTest < TestCase
)
click(
browser: customer,
css: '.js-chat-toggle',
css: '.js-chat-toggle .zammad-chat-header-icon',
)
watch_for(
browser: agent,
@ -401,7 +401,7 @@ class ChatTest < TestCase
)
click(
browser: customer,
css: '.zammad-chat .js-chat-toggle',
css: '.zammad-chat .js-chat-toggle .zammad-chat-header-icon',
)
watch_for_disappear(
browser: customer,
@ -412,7 +412,7 @@ class ChatTest < TestCase
sleep 2
click(
browser: customer,
css: '.js-chat-open',
css: '.zammad-chat .js-chat-open',
)
exists(
browser: customer,
@ -581,7 +581,7 @@ class ChatTest < TestCase
)
click(
browser: customer,
css: '.js-chat-toggle',
css: '.zammad-chat .js-chat-toggle .zammad-chat-header-icon',
)
watch_for(
browser: agent,

View file

@ -20,7 +20,8 @@ class SwitchToUserTest < TestCase
)
sleep 3
@browser.mouse.move_to(@browser.find_elements({ css: '.content.active .table-overview tbody tr:first-child' } )[0])
@browser.action.move_to(@browser.find_elements({ css: '.content.active .table-overview tbody tr:first-child' } )[0]).release.perform
sleep 0.5
click(
css: '.content.active .icon-switchView',

View file

@ -0,0 +1,376 @@
require 'browser_test_helper'
class AgentProfilePermissionsTest < TestCase
def test_agent_to_edit_customer_profile
@browser = browser_instance
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
# search and open user
user_open_by_search(value: 'Braun')
verify_task(
data: {
title: 'Nicole Braun',
}
)
watch_for(
css: '.content.active .profile-window',
value: 'note',
)
watch_for(
css: '.content.active .profile-window',
value: 'email',
)
# update note
set(
css: '.content.active [data-name="note"]',
value: 'some note 123',
)
empty_search()
# check and change note again in edit screen
click(css: '.content.active .js-action .icon-arrow-down', fast: true)
click(css: '.content.active .js-action [data-type="edit"]')
watch_for(
css: '.content.active .modal',
value: 'note',
)
watch_for(
css: '.content.active .modal',
value: 'some note 123',
)
set(
css: '.modal [name="lastname"]',
value: 'B2',
)
set(
css: '.modal [data-name="note"]',
value: 'some note abc',
)
click(css: '.content.active .modal button.js-submit')
watch_for(
css: '.content.active .profile-window',
value: 'some note abc',
)
verify_task(
data: {
title: 'Nicole B2',
}
)
# change lastname back
click(css: '.content.active .js-action .icon-arrow-down', fast: true)
click(css: '.content.active .js-action [data-type="edit"]')
watch_for(
css: '.content.active .modal',
value: 'note',
)
set(
css: '.modal [name="lastname"]',
value: 'Braun',
)
click(css: '.content.active .modal button.js-submit')
verify_task(
data: {
title: 'Nicole Braun',
}
)
end
def test_agent_edit_admin_profile
@browser = browser_instance
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
# search and open user
user_open_by_search(value: 'Test Master')
verify_task(
data: {
title: 'Test Master Agent',
}
)
watch_for(
css: '.content.active .profile-window',
value: 'note',
)
watch_for(
css: '.content.active .profile-window',
value: 'email',
)
empty_search()
sleep 2
click(css: '.content.active .js-action .icon-arrow-down', fast: true)
exists_not(
css: '.content.active .js-action [data-type="edit"]'
)
end
def test_agent_to_edit_admin_ticket_user_details
@browser = browser_instance
login(
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
ticket1 = ticket_create(
data: {
customer: 'master',
group: 'Users',
title: 'test_auto_assignment_1 - ticket 1',
body: 'test_auto_assignment_1 - ticket 1 - no auto assignment',
},
)
tasks_close_all()
logout()
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
# open ticket#1
ticket_open_by_search(
number: ticket1[:number],
)
watch_for(
css: '.content.active .tabsSidebar-holder',
value: ticket1[:title],
)
click(css: '.content.active .tabsSidebar .tabsSidebar-tab[data-tab="customer"]')
click(css: '.content.active .sidebar[data-tab="customer"] .js-actions .dropdown-toggle')
exists_not(css: '.content.active .sidebar[data-tab="customer"] .js-actions [data-type="customer-edit"]')
end
def test_agent_to_edit_customer_ticket
@browser = browser_instance
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
ticket1 = ticket_create(
data: {
customer: 'nico',
group: 'Users',
title: 'test_auto_assignment_2 - ticket 2',
body: 'test_auto_assignment_2 - ticket 2 - no auto assignment',
},
)
# open ticket#1
ticket_open_by_search(
number: ticket1[:number],
)
click(css: '.content.active .tabsSidebar .tabsSidebar-tab[data-tab="customer"]')
click(css: '.content.active .sidebar[data-tab="customer"] .js-actions .dropdown-toggle')
click(css: '.content.active .sidebar[data-tab="customer"] .js-actions [data-type="customer-edit"]')
set(
css: '.modal [name="lastname"]',
value: 'B2',
)
set(
css: '.modal [data-name="note"]',
value: 'some note abc',
)
click(css: '.content.active .modal button.js-submit')
watch_for(
css: '.content.active .sidebar[data-tab="customer"] .sidebar-block [data-name="note"]',
value: 'some note abc',
)
watch_for(
css: '.content.active .sidebar[data-tab="customer"] .sidebar-block h3[title="Name"]',
value: 'Nicole B2',
)
sleep 2
# change lastname back
click(css: '.content.active .sidebar[data-tab="customer"] .js-actions')
click(css: 'li[data-type="customer-edit"]')
watch_for(
css: '.content.active .modal',
value: 'note',
)
set(
css: '.modal [name="lastname"]',
value: 'Braun',
)
set(
css: '.modal [data-name="note"]',
value: 'some note abc',
)
click(css: '.content.active .modal button.js-submit')
watch_for(
css: '.content.active .sidebar[data-tab="customer"] .sidebar-block [data-name="note"]',
value: 'some note abc',
)
watch_for(
css: '.content.active .sidebar[data-tab="customer"] .sidebar-block [title="Name"]',
value: 'Nicole Braun',
)
end
def test_agent_to_edit_customer_ticket_details
@browser = browser_instance
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
ticket1 = ticket_create(
data: {
customer: 'nico',
group: 'Users',
title: 'test_auto_assignment_2 - ticket 2',
body: 'test_auto_assignment_2 - ticket 2 - no auto assignment',
},
)
# open ticket#1
ticket_open_by_search(
number: ticket1[:number],
)
exists(css: '.content.active .tabsSidebar .tabsSidebar-tab[data-tab="customer"]')
exists(css: '.content.active .sidebar[data-tab="customer"] .js-actions .dropdown-toggle')
exists(css: '.content.active .sidebar[data-tab="customer"] .js-actions [data-type="customer-edit"]')
click(css: '.content.active .tabsSidebar-holder .js-avatar')
# check and change note again in edit screen
click(css: '.content.active .js-action .dropdown-toggle')
click(css: '.content.active .js-action [data-type="edit"]')
watch_for(
css: '.content.active .modal',
value: 'note',
)
set(
css: '.modal [name="lastname"]',
value: 'B2',
)
set(
css: '.modal [data-name="note"]',
value: 'some note abc',
)
click(css: '.content.active .modal button.js-submit')
watch_for(
css: '.content.active .profile-window',
value: 'some note abc',
)
verify_task(
data: {
title: 'Nicole B2',
}
)
# change lastname back
click(css: '.content.active .js-action .dropdown-toggle')
click(css: '.content.active .js-action [data-type="edit"]')
watch_for(
css: '.content.active .modal',
value: 'note',
)
set(
css: '.modal [name="lastname"]',
value: 'Braun',
)
set(
css: '.modal [data-name="note"]',
value: 'note',
)
click(css: '.content.active .modal button.js-submit')
verify_task(
data: {
title: 'Nicole Braun',
}
)
end
def test_agent_to_edit_admin_ticket_details
@browser = browser_instance
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
ticket1 = ticket_create(
data: {
customer: 'master',
group: 'Users',
title: 'test_auto_assignment_2 - ticket 2',
body: 'test_auto_assignment_2 - ticket 2 - no auto assignment',
},
)
# open ticket#1
ticket_open_by_search(
number: ticket1[:number],
)
exists(css: '.content.active .tabsSidebar .tabsSidebar-tab[data-tab="customer"]')
exists(css: '.content.active .sidebar[data-tab="customer"] .js-actions .dropdown-toggle')
exists_not(css: '.content.active .sidebar[data-tab="customer"] .js-actions [data-type="customer-edit"]')
click(css: '.content.active .tabsSidebar-holder .js-avatar')
# check and change note again in edit screen
click(css: '.content.active .js-action .icon-arrow-down', fast: true)
exists_not(
css: '.content.active .js-action [data-type="edit"]'
)
end
end

View file

@ -422,7 +422,7 @@ class TestCase < Test::Unit::TestCase
element = instance.find_elements(css: params[:css])[0]
return if !element && params[:only_if_exists] == true
#if element
# instance.mouse.move_to(element)
# instance.action.move_to(element).release.perform
#end
element.click
rescue => e
@ -433,8 +433,9 @@ class TestCase < Test::Unit::TestCase
element = instance.find_elements(css: params[:css])[0]
return if !element && params[:only_if_exists] == true
#if element
# instance.mouse.move_to(element)
# instance.action.move_to(element).release.perform
#end
raise "No such element '#{params[:css]}'" if !element
element.click
end
@ -447,7 +448,9 @@ class TestCase < Test::Unit::TestCase
# just try again
log('click', { rescure: true })
instance.find_elements(partial_link_text: params[:text])[0].click
element = instance.find_elements(partial_link_text: params[:text])[0]
raise "No such element '#{params[:text]}'" if !element
element.click
end
end
sleep 0.2 if !params[:fast]
@ -632,6 +635,7 @@ class TestCase < Test::Unit::TestCase
end
element.clear
begin
if !params[:slow]
element.send_keys(params[:value])
else
@ -641,17 +645,28 @@ class TestCase < Test::Unit::TestCase
instance.action.send_keys(key).perform
end
end
rescue => e
sleep 0.5
# just try again
log('set', { rescure: true })
element = instance.find_elements(css: params[:css])[0]
raise "No such element '#{params[:css]}'" if !element
if !params[:slow]
element.send_keys(params[:value])
else
element.send_keys('')
keys = params[:value].to_s.split('')
keys.each do |key|
instance.action.send_keys(key).perform
end
end
end
# it's not working stable with ff via selenium, use js
if browser =~ /firefox/i && params[:css] =~ /\[data-name=/
log('set_ff_check', params)
value = instance.find_elements(css: params[:css])[0].text
if value != params[:value]
log('set_ff_check_failed_use_js', params)
value_quoted = quote(params[:value])
puts "DEBUG $('#{params[:css]}').html('#{value_quoted}').trigger('focusout')"
instance.execute_script("$('#{params[:css]}').html('#{value_quoted}').trigger('focusout')")
end
log('set_ff_trigger_workaround', params)
instance.execute_script("$('#{params[:css]}').trigger('focusout')")
end
if params[:blur]
@ -1159,12 +1174,15 @@ set type of task (closeTab, closeNextInOverview, stayOnTab)
instance = params[:browser] || @browser
data = params[:data]
element = instance.find_elements(partial_link_text: data[:title])[0]
element = instance.find_element(css: '#navigation').find_element(partial_link_text: data[:title])
if !element
screenshot(browser: instance, comment: 'open_task_failed')
raise "no task with title '#{data[:title]}' found"
end
element.click
# firefix/marionette issue with Selenium::WebDriver::Error::ElementNotInteractableError: could not be scrolled into view
# use js workaround instead of native click
instance.execute_script("$('#navigation .tasks .task:contains(\"#{data[:title]}\") .nav-tab-name').click()")
#element.click
true
end
@ -1187,15 +1205,15 @@ set type of task (closeTab, closeNextInOverview, stayOnTab)
instance = params[:browser] || @browser
data = params[:data]
element = instance.find_elements(partial_link_text: data[:title])[0]
element = instance.find_element(css: '#navigation').find_element(partial_link_text: data[:title])
if !element
screenshot(browser: instance, comment: 'close_task_failed')
raise "no task with title '#{data[:title]}' found"
end
instance.mouse.move_to(element)
instance.action.move_to(element).release.perform
sleep 0.1
instance.execute_script("$('.navigation .tasks .task:contains(\"#{data[:title]}\") .js-close').click()")
instance.execute_script("$('#navigation .tasks .task:contains(\"#{data[:title]}\") .js-close').click()")
# accept task close warning
if params[:discard_changes]
@ -1405,10 +1423,9 @@ wait untill text in selector disabppears
99.times do
#sleep 0.5
begin
if instance.find_elements(css: '.navigation .tasks .task:first-child')[0]
instance.mouse.move_to(instance.find_elements(css: '.navigation .tasks .task:first-child')[0])
sleep 0.1
click_element = instance.find_elements(css: '.navigation .tasks .task:first-child .js-close')[0]
if instance.find_elements(css: '#navigation .tasks .task:first-child')[0]
instance.action.move_to(instance.find_elements(css: '#navigation .tasks .task:first-child')[0]).release.perform
click_element = instance.find_elements(css: '#navigation .tasks .task:first-child .js-close')[0]
if click_element
click_element.click
@ -1453,7 +1470,7 @@ wait untill text in selector disabppears
screenshot(browser: instance, comment: 'close_online_notitifcation')
raise "no online notification with title '#{data[:title]}' found"
end
instance.mouse.move_to(element)
instance.action.move_to(element).release.perform
sleep 0.1
instance.execute_script("$('.js-notificationsContainer .js-items .js-item .activity-text:contains(\"#{data[:title]}\") .js-remove').first().click()")
@ -1465,7 +1482,7 @@ wait untill text in selector disabppears
raise "no online notification with postion '#{css}' found"
end
instance.mouse.move_to(element)
instance.action.move_to(element).release.perform
sleep 0.1
instance.find_elements(css: "#{css} .js-remove")[0].click
end
@ -1491,7 +1508,7 @@ wait untill text in selector disabppears
sleep 0.5
begin
if instance.find_elements(css: '.js-notificationsContainer .js-item:first-child')[0]
instance.action.move_to(instance.find_elements(css: '.js-notificationsContainer .js-item:first-child')[0])
instance.action.move_to(instance.find_elements(css: '.js-notificationsContainer .js-item:first-child')[0]).perform
sleep 0.1
click_element = instance.find_elements(css: '.js-notificationsContainer .js-item:first-child .js-remove')[0]
click_element&.click
@ -2361,7 +2378,7 @@ wait untill text in selector disabppears
)
screenshot(browser: instance, comment: 'ticket_open_by_overview_search')
if params[:title]
element = instance.find_elements(partial_link_text: params[:title])[0]
element = instance.find_element(css: '.content.active').find_element(partial_link_text: params[:title])
if !element
screenshot(browser: instance, comment: 'ticket_open_by_overview_no_ticket_failed')
raise "unable to find ticket #{params[:title]} in overview #{params[:link]}!"