Added enhanced Group access functionality.

This commit is contained in:
Thorsten Eckel 2017-06-16 22:43:09 +02:00
parent 873998ec5a
commit 350c3ead51
78 changed files with 2041 additions and 496 deletions

View file

@ -52,6 +52,7 @@ gem 'mime-types'
gem 'biz'
gem 'composite_primary_keys'
gem 'delayed_job_active_record'
gem 'daemons'

View file

@ -75,6 +75,8 @@ GEM
coffee-script
execjs
json
composite_primary_keys (8.1.5)
activerecord (~> 4.2.0)
concurrent-ruby (1.0.2)
coveralls (0.8.16)
json (>= 1.8, < 3)
@ -430,6 +432,7 @@ DEPENDENCIES
coffee-rails
coffee-script-source
coffeelint
composite_primary_keys
coveralls
daemons
delayed_job_active_record

View file

@ -75,13 +75,14 @@ class Index extends App.ControllerSubContent
groups = []
group_ids = @Session.get('group_ids')
if group_ids
for group_id in group_ids
group = App.Group.find(group_id)
groups.push group
if !user_group_config
if !config['group_ids']
config['group_ids'] = []
config['group_ids'].push group_id.toString()
for group_id, access of group_ids
if _.contains(access, 'full')
group = App.Group.find(group_id)
groups.push group
if !user_group_config
if !config['group_ids']
config['group_ids'] = []
config['group_ids'].push group_id.toString()
for sound in @sounds
sound.selected = sound.file is App.OnlineNotification.soundFile() ? true : false
@ -90,7 +91,7 @@ class Index extends App.ControllerSubContent
groups: groups
config: config
sounds: @sounds
notification_sound_enabled: App.OnlineNotification.soundEnabled()
notificationSoundEnabled: App.OnlineNotification.soundEnabled()
update: (e) =>

View file

@ -4,10 +4,25 @@ class App.UiElement.permission extends App.UiElement.ApplicationUiElement
permissions = App.Permission.search(sortBy: 'name')
# get selectable groups and selected groups
groups = []
groupsSelected = {}
groupsRaw = App.Group.search(sortBy: 'name')
for group in groupsRaw
if group.active
groups.push group
if params.group_ids
for group_id in params.group_ids
if group_id.toString() is group.id.toString()
groupsSelected[group.id] = true
item = $( App.view('generic/permission')(
attribute: attribute
params: params
permissions: permissions
groups: groups
groupsSelected: groupsSelected
groupAccesses: App.Group.accesses()
) )
# show/hide trees
@ -37,4 +52,4 @@ class App.UiElement.permission extends App.UiElement.ApplicationUiElement
)
item
item

View file

@ -72,6 +72,7 @@ class App.UiElement.user_permission
rolesSelected: rolesSelected
groupsSelected: groupsSelected
hideGroups: hideGroups
groupAccesses: App.Group.accesses()
) )
# if customer, remove admin and agent
@ -105,7 +106,7 @@ class App.UiElement.user_permission
# select groups if only one is available
if hideGroups
item.find('.js-groupList [name=group_ids]').prop('checked', false)
item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', false)
return
# if role with groups plugin is selected, show group selection
@ -114,7 +115,7 @@ class App.UiElement.user_permission
# select groups if only one is available
if hideGroups
item.find('.js-groupList [name=group_ids]').prop('checked', true)
item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', true)
for trigger in triggers
trigger.trigger('change')

View file

@ -476,10 +476,11 @@ class App.TicketCreate extends App.Controller
ui.scrollTo()
# access to group
group_ids = _.map(App.Session.get('group_ids'), (id) -> id.toString())
if group_ids && _.contains(group_ids, @group_id.toString())
ui.navigate "#ticket/zoom/#{@id}"
return
for group_id, access of App.Session.get('group_ids')
if @group_id.toString() is group_id.toString()
if _.contains(access, 'read') || _.contains(access, 'full')
ui.navigate "#ticket/zoom/#{@id}"
return
# if not, show start screen
ui.navigate '#'

View file

@ -34,4 +34,12 @@ class App.Group extends App.Model
cssClass.push("avatar--group-color-#{@id % 3}")
return App.view('avatar_group')
cssClass: cssClass.join(' ')
cssClass: cssClass.join(' ')
@accesses: ->
read: 'Read'
create: 'Create'
change: 'Change'
delete: 'Delete'
overview: 'Overview'
full: 'Full'

View file

@ -1,5 +1,5 @@
class App.Role extends App.Model
@configure 'Role', 'name', 'permission_ids', 'default_at_signup', 'note', 'active', 'updated_at'
@configure 'Role', 'name', 'permission_ids', 'group_ids', 'default_at_signup', 'note', 'active', 'updated_at'
@extend Spine.Model.Ajax
@url: @apiPath + '/roles'
@configure_attributes = [

View file

@ -15,6 +15,36 @@
<%- @Icon('checkbox-checked', 'icon-checked') %>
<span class="label-text"><%= permission.displayName().replace(/^.+?\./, '') %> - <span class="help-text"><%- @T.apply(@, [permission.note].concat(permission.preferences.translations)) %></span></span>
</label>
<% if _.contains(permission.preferences.plugin, 'groups'): %>
<div style="padding-left: 18px; padding-top: 10px;" class="js-groupList <% if @hideGroups: %>js-groupListHide hidden<% end %>">
<table class="settings-list">
<thead>
<th><%- @T('Group') %>
<% for key, text of @groupAccesses: %>
<th><%- @T(text) %>
<% end %>
<tbody>
<% for group in @groups: %>
<% accesses = [] %>
<% if @params.group_ids && @params.group_ids[group.id]: %>
<% accesses = @params.group_ids[group.id] %>
<% end %>
<tr>
<td>
<%= group.displayName() %>
<% for key, text of @groupAccesses: %>
<td>
<label class="inline-label checkbox-replacement">
<input type="checkbox" value="<%= key %>" name="group_ids::<%= group.id %>" <% if _.contains(accesses, key): %>checked<% end %>/>
<%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %>
</label>
<% end %>
</tr>
<% end %>
</table>
</div>
<% end %>
</div>
<% end %>
<% end %>

View file

@ -1,4 +1,18 @@
<div class="checkbox <%= @attribute.class %> checkbox">
<% showGroups = false %>
<% for role in @roles: %>
<% if role.permissions: %>
<% for permission in role.permissions: %>
<% if _.contains(permission.preferences.plugin, 'groups'): %>
<% if showGroups is true: %>
<% showGroups = false %>
<% break %>
<% end %>
<% showGroups = true %>
<% end %>
<% end %>
<% end %>
<% end %>
<% for role in @roles: %>
<label class="inline-label checkbox-replacement">
<input type="checkbox" value="<%= role.id %>" name="role_ids" <% if @rolesSelected[role.id]: %>checked<% end %>/>
@ -8,20 +22,38 @@
</label>
<% if role.permissions: %>
<% for permission in role.permissions: %>
<% if _.contains(permission.preferences.plugin, 'groups'): %>
<div style="padding-left: 20px;" class="js-groupList <% if @hideGroups: %>js-groupListHide hidden<% end %>">
<% if showGroups is true && _.contains(permission.preferences.plugin, 'groups'): %>
<div style="padding-left: 18px; padding-top: 10px;" class="js-groupList <% if @hideGroups: %>js-groupListHide hidden<% end %>">
<table class="settings-list">
<thead>
<th><%- @T('Group') %>
<% for key, text of @groupAccesses: %>
<th><%- @T(text) %>
<% end %>
<tbody>
<% for group in @groups: %>
<label class="inline-label checkbox-replacement">
<input type="checkbox" value="<%= group.id %>" name="group_ids" <% if @groupsSelected[group.id]: %>checked<% end %>/>
<%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %>
<span class="label-text"><%= group.displayName() %> <% if group.note: %>- <span class="help-text"><%- group.note %></span><% end %></span>
</label>
<% permissions = [] %>
<% if @params.group_ids && @params.group_ids[group.id]: %>
<% permissions = @params.group_ids[group.id] %>
<% end %>
<tr>
<td>
<%= group.displayName() %>
<% for key, text of @groupAccesses: %>
<td>
<label class="inline-label checkbox-replacement">
<input class="js-groupListItem" type="checkbox" value="<%= key %>" name="group_ids::<%= group.id %>" <% if _.contains(permissions, key): %>checked<% end %>/>
<%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %>
</label>
<% end %>
</tr>
<% end %>
</table>
</div>
<% break %>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
</div>

View file

@ -92,7 +92,7 @@
<div class="form-group">
<label class="inline-label">
<span class="checkbox-replacement checkbox-replacement--inline">
<input type="checkbox" name="notification_sound::enabled" value="true" <% if @notification_sound_enabled: %> checked<% end %> class="js-SoundEnableDisable">
<input type="checkbox" name="notification_sound::enabled" value="true" <% if @notificationSoundEnabled: %> checked<% end %> class="js-SoundEnableDisable">
<%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %>
</span>
@ -101,4 +101,4 @@
</div>
<button type="submit" class="btn btn--primary"><%- @T( 'Submit' ) %></button>
</form>
</form>

View file

@ -12,4 +12,5 @@ class ApplicationController < ActionController::Base
include ApplicationController::HasUser
include ApplicationController::PreventsCsrf
include ApplicationController::LogsHttpAccess
include ApplicationController::ChecksAccess
end

View file

@ -0,0 +1,9 @@
module ApplicationController::ChecksAccess
extend ActiveSupport::Concern
private
def access!(instance, access)
instance.access!(current_user, access)
end
end

View file

@ -1,10 +0,0 @@
module AccessesTickets
extend ActiveSupport::Concern
private
def ticket_permission(ticket)
return true if ticket.permission(current_user: current_user)
raise Exceptions::NotAuthorized
end
end

View file

@ -1,7 +1,6 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class TicketArticlesController < ApplicationController
include AccessesTickets
include CreatesTicketArticles
prepend_before_action :authentication_check
@ -15,7 +14,7 @@ class TicketArticlesController < ApplicationController
# GET /articles/1
def show
article = Ticket::Article.find(params[:id])
article_permission(article)
access!(article, 'read')
if params[:expand]
result = article.attributes_with_association_names
@ -35,7 +34,7 @@ class TicketArticlesController < ApplicationController
# GET /ticket_articles/by_ticket/1
def index_by_ticket
ticket = Ticket.find(params[:id])
ticket_permission(ticket)
access!(ticket, 'read')
articles = []
@ -82,7 +81,7 @@ class TicketArticlesController < ApplicationController
# POST /articles
def create
ticket = Ticket.find(params[:ticket_id])
ticket_permission(ticket)
access!(ticket, 'create')
article = article_create(ticket, params)
if params[:expand]
@ -103,7 +102,7 @@ class TicketArticlesController < ApplicationController
# PUT /articles/1
def update
article = Ticket::Article.find(params[:id])
article_permission(article)
access!(article, 'change')
if !current_user.permissions?('ticket.agent') && !current_user.permissions?('admin')
raise Exceptions::NotAuthorized, 'Not authorized (ticket.agent or admin permission required)!'
@ -132,7 +131,7 @@ class TicketArticlesController < ApplicationController
# DELETE /articles/1
def destroy
article = Ticket::Article.find(params[:id])
article_permission(article)
access!(article, 'delete')
if current_user.permissions?('admin')
article.destroy!
@ -209,9 +208,8 @@ class TicketArticlesController < ApplicationController
# GET /ticket_attachment/:ticket_id/:article_id/:id
def attachment
ticket = Ticket.lookup(id: params[:ticket_id])
if !ticket_permission(ticket)
raise Exceptions::NotAuthorized, 'No such ticket.'
end
access!(ticket, 'read')
article = Ticket::Article.find(params[:article_id])
if ticket.id != article.ticket_id
@ -221,9 +219,7 @@ class TicketArticlesController < ApplicationController
end
ticket = article.ticket
if !ticket_permission(ticket)
raise Exceptions::NotAuthorized, "No access, for ticket_id '#{ticket.id}'."
end
access!(ticket, 'read')
end
list = article.attachments || []
@ -251,7 +247,7 @@ class TicketArticlesController < ApplicationController
# GET /ticket_article_plain/1
def article_plain
article = Ticket::Article.find(params[:id])
article_permission(article)
access!(article, 'read')
file = article.as_raw
@ -268,15 +264,6 @@ class TicketArticlesController < ApplicationController
private
def article_permission(article)
if current_user.permissions?('ticket.customer')
raise Exceptions::NotAuthorized if article.internal == true
end
ticket = Ticket.lookup(id: article.ticket_id)
return true if ticket.permission(current_user: current_user)
raise Exceptions::NotAuthorized
end
def sanitized_disposition
disposition = params.fetch(:disposition, 'inline')
valid_disposition = %w(inline attachment)

View file

@ -1,7 +1,6 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class TicketsController < ApplicationController
include AccessesTickets
include CreatesTicketArticles
include TicketStats
@ -21,7 +20,7 @@ class TicketsController < ApplicationController
per_page = 100
end
access_condition = Ticket.access_condition(current_user)
access_condition = Ticket.access_condition(current_user, 'read')
tickets = Ticket.where(access_condition).order(id: 'ASC').offset(offset).limit(per_page)
if params[:expand]
@ -52,10 +51,8 @@ class TicketsController < ApplicationController
# GET /api/v1/tickets/1
def show
# permission check
ticket = Ticket.find(params[:id])
ticket_permission(ticket)
access!(ticket, 'read')
if params[:expand]
result = ticket.attributes_with_association_names
@ -180,10 +177,8 @@ class TicketsController < ApplicationController
# PUT /api/v1/tickets/1
def update
# permission check
ticket = Ticket.find(params[:id])
ticket_permission(ticket)
access!(ticket, 'change')
clean_params = Ticket.association_name_to_id_convert(params)
clean_params = Ticket.param_cleanup(clean_params, true)
@ -218,10 +213,8 @@ class TicketsController < ApplicationController
# DELETE /api/v1/tickets/1
def destroy
# permission check
ticket = Ticket.find(params[:id])
ticket_permission(ticket)
access!(ticket, 'delete')
raise Exceptions::NotAuthorized, 'Not authorized (admin permission required)!' if !current_user.permissions?('admin')
@ -247,9 +240,7 @@ class TicketsController < ApplicationController
# get ticket data
ticket = Ticket.find(params[:id])
# permission check
ticket_permission(ticket)
access!(ticket, 'read')
# get history of ticket
history = ticket.history_get(true)
@ -265,7 +256,7 @@ class TicketsController < ApplicationController
assets = ticket.assets({})
# open tickets by customer
access_condition = Ticket.access_condition(current_user)
access_condition = Ticket.access_condition(current_user, 'read')
ticket_lists = Ticket
.where(
@ -328,9 +319,7 @@ class TicketsController < ApplicationController
}
return
end
# permission check
ticket_permission(ticket_master)
access!(ticket_master, 'full')
# check slave ticket
ticket_slave = Ticket.find_by(id: params[:slave_ticket_id])
@ -341,11 +330,9 @@ class TicketsController < ApplicationController
}
return
end
access!(ticket_slave, 'full')
# permission check
ticket_permission(ticket_slave)
# check diffetent ticket ids
# check different ticket ids
if ticket_slave.id == ticket_master.id
render json: {
result: 'failed',
@ -370,10 +357,8 @@ class TicketsController < ApplicationController
# GET /api/v1/ticket_split
def ticket_split
# permission check
ticket = Ticket.find(params[:ticket_id])
ticket_permission(ticket)
access!(ticket, 'read')
assets = ticket.assets({})
# get related articles
@ -390,7 +375,7 @@ class TicketsController < ApplicationController
# get attributes to update
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(
user: current_user,
current_user: current_user,
)
render json: attributes_to_change
end
@ -483,7 +468,7 @@ class TicketsController < ApplicationController
# lookup open user tickets
limit = 100
assets = {}
access_condition = Ticket.access_condition(current_user)
access_condition = Ticket.access_condition(current_user, 'read')
user_tickets = {}
if params[:user_id]
@ -578,7 +563,10 @@ class TicketsController < ApplicationController
def ticket_all(ticket)
# get attributes to update
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(user: current_user, ticket: ticket)
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(
current_user: current_user,
ticket: ticket
)
# get related users
assets = attributes_to_change[:assets]

View file

@ -75,25 +75,22 @@ class UsersController < ApplicationController
# @response_message 200 [User] User record matching the requested identifier.
# @response_message 401 Invalid session.
def show
# access deny
permission_check_local
user = User.find(params[:id])
access!(user, 'read')
if params[:expand]
user = User.find(params[:id]).attributes_with_association_names
render json: user, status: :ok
return
result = user.attributes_with_association_names
elsif params[:full]
result = {
id: params[:id],
assets: user.assets({}),
}
else
result = user.attributes_with_association_ids
result.delete('password')
end
if params[:full]
full = User.full(params[:id])
render json: full
return
end
user = User.find(params[:id]).attributes_with_association_ids
user.delete('password')
render json: user
render json: result
end
# @path [POST] /users
@ -108,8 +105,6 @@ class UsersController < ApplicationController
def create
clean_params = User.association_name_to_id_convert(params)
clean_params = User.param_cleanup(clean_params, true)
user = User.new(clean_params)
user.associations_from_param(params)
# check if it's first user, the admin user
# inital admin account
@ -131,6 +126,8 @@ class UsersController < ApplicationController
if admin_account_exists && !params[:signup]
raise Exceptions::UnprocessableEntity, 'Only signup with not authenticate user possible!'
end
user = User.new(clean_params)
user.associations_from_param(params)
user.updated_by_id = 1
user.created_by_id = 1
@ -164,12 +161,8 @@ class UsersController < ApplicationController
# permission check
permission_check_by_permission(params)
if params[:role_ids]
user.role_ids = params[:role_ids]
end
if params[:group_ids]
user.group_ids = params[:group_ids]
end
user = User.new(clean_params)
user.associations_from_param(params)
end
# check if user already exists
@ -245,28 +238,25 @@ class UsersController < ApplicationController
# @response_message 200 [User] Updated User record.
# @response_message 401 Invalid session.
def update
# access deny
permission_check_local
permission_check_by_permission(params)
user = User.find(params[:id])
clean_params = User.association_name_to_id_convert(params)
clean_params = User.param_cleanup(clean_params, true)
access!(user, 'change')
# permission check
permission_check_by_permission(params)
user.with_lock do
clean_params = User.association_name_to_id_convert(params)
clean_params = User.param_cleanup(clean_params, true)
user.update_attributes(clean_params)
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:role_ids] || params[:roles])
user.role_ids = params[:role_ids]
user.associations_from_param({ role_ids: params[:role_ids], roles: params[:roles] })
end
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:group_ids] || params[:groups])
user.group_ids = params[:group_ids]
user.associations_from_param({ group_ids: params[:group_ids], groups: params[:groups] })
end
@ -298,7 +288,9 @@ class UsersController < ApplicationController
# @response_message 200 User successfully deleted.
# @response_message 401 Invalid session.
def destroy
permission_check('admin.user')
user = User.find(params[:id])
access!(user, 'delete')
model_references_check(User, params)
model_destroy_render(User, params)
end
@ -1006,30 +998,25 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
def permission_check_by_permission(params)
return true if current_user.permissions?('admin.user')
if !current_user.permissions?('admin.user') && params[:role_ids]
if params[:role_ids].class != Array
params[:role_ids] = [params[:role_ids]]
end
params[:role_ids].each { |role_id|
role_local = Role.lookup(id: role_id)
if !role_local
logger.info "Invalid role_ids for current_user_id: #{current_user.id} role_ids #{role_id}"
raise Exceptions::NotAuthorized, 'Invalid role_ids!'
end
role_name = role_local.name
# TODO: check role permissions
next if role_name != 'Admin' && role_name != 'Agent'
logger.info "This role assignment is only allowed by admin! current_user_id: #{current_user.id} assigned to #{role_name}"
%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!'
}
end
end
if current_user.permissions?('ticket.agent') && !params[:role_ids] && !params[:roles] && params[:id].blank?
params[:role_ids] = Role.signup_role_ids
end
if !current_user.permissions?('admin.user') && params[:group_ids]
if params[:group_ids].class != Array
params[:group_ids] = [params[:group_ids]]
end
if !params[:group_ids].empty?
logger.info "Group relation is only allowed by admin! current_user_id: #{current_user.id} group_ids #{params[:group_ids].inspect}"
%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
@ -1039,16 +1026,4 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
response_access_deny
false
end
def permission_check_local
return true if current_user.permissions?('admin.user')
return true if current_user.permissions?('ticket.agent')
# allow to update any by him self
# TODO check certain attributes like roles_ids and group_ids
return true if params[:id].to_i == current_user.id
raise Exceptions::NotAuthorized
end
end

View file

@ -97,7 +97,7 @@ return all activity entries of an user
return [] if !user.permissions?('ticket.agent') && !user.permissions?('admin')
permission_ids = user.permissions_with_child_ids
group_ids = user.group_ids
group_ids = user.group_ids_access('read')
stream = if group_ids.empty?
ActivityStream.where('(permission_id IN (?) AND group_id is NULL)', permission_ids)

View file

@ -17,9 +17,21 @@ returns
def associations_from_param(params)
# special handling for group access association
{
groups: :group_names_access_map=,
group_ids: :group_ids_access_map=
}.each do |param, setter|
map = params[param]
next if map.blank?
next if !respond_to?(setter)
send(setter, map)
end
# set relations by id/verify if ref exists
self.class.reflect_on_all_associations.map { |assoc|
assoc_name = assoc.name
next if association_attributes_ignored.include?(assoc_name)
real_ids = assoc_name[0, assoc_name.length - 1] + '_ids'
real_ids = real_ids.to_sym
next if !params.key?(real_ids)
@ -44,6 +56,7 @@ returns
# set relations by name/lookup
self.class.reflect_on_all_associations.map { |assoc|
assoc_name = assoc.name
next if association_attributes_ignored.include?(assoc_name)
real_ids = assoc_name[0, assoc_name.length - 1] + '_ids'
next if !respond_to?(real_ids)
real_values = assoc_name[0, assoc_name.length - 1] + 's'
@ -95,17 +108,20 @@ returns
cache = Cache.get(key)
return cache if cache
ignored_attributes = self.class.instance_variable_get(:@association_attributes_ignored) || []
# get relations
attributes = self.attributes
self.class.reflect_on_all_associations.map { |assoc|
next if association_attributes_ignored.include?(assoc.name)
real_ids = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
next if ignored_attributes.include?(real_ids.to_sym)
next if !respond_to?(real_ids)
attributes[real_ids] = send(real_ids)
}
# special handling for group access associations
if respond_to?(:group_ids_access_map)
attributes['group_ids'] = send(:group_ids_access_map)
end
filter_attributes(attributes)
Cache.write(key, attributes)
@ -131,6 +147,7 @@ returns
attributes = attributes_with_association_ids
self.class.reflect_on_all_associations.map { |assoc|
next if !respond_to?(assoc.name)
next if association_attributes_ignored.include?(assoc.name)
ref = send(assoc.name)
next if !ref
if ref.respond_to?(:first)
@ -156,6 +173,11 @@ returns
attributes[assoc.name.to_s] = ref[:name]
}
# special handling for group access associations
if respond_to?(:group_names_access_map)
attributes['groups'] = send(:group_names_access_map)
end
# fill created_by/updated_by
{
'created_by_id' => 'created_by',
@ -214,6 +236,12 @@ returns
true
end
private
def association_attributes_ignored
@association_attributes_ignored ||= self.class.instance_variable_get(:@association_attributes_ignored) || []
end
# methods defined here are going to extend the class, not the instance of it
class_methods do
@ -223,13 +251,14 @@ serve methode to ignore model attribute associations
class Model < ApplicationModel
include AssociationConcern
association_attributes_ignored :user_ids
association_attributes_ignored :users
end
=end
def association_attributes_ignored(*attributes)
@association_attributes_ignored = attributes
@association_attributes_ignored ||= []
@association_attributes_ignored |= attributes
end
=begin

View file

@ -13,7 +13,7 @@ module ApplicationModel::ChecksImport
# do noting, use id as it is
return if !Setting.get('system_init_done')
return if Setting.get('import_mode') && import_class_list.include?(self.class.to_s)
return if !has_attribute?(:id)
self[:id] = nil
end
end

View file

@ -14,6 +14,7 @@ module ApplicationModel::HasCache
def cache_update(o)
cache_delete if respond_to?('cache_delete')
o.cache_delete if o.respond_to?('cache_delete')
true
end
def cache_delete
@ -52,6 +53,7 @@ module ApplicationModel::HasCache
Cache.delete(key)
end
end
true
end
# methods defined here are going to extend the class, not the instance of it

View file

@ -0,0 +1,331 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
module HasGroups
extend ActiveSupport::Concern
included do
before_destroy :destroy_group_relations
attr_accessor :group_access_buffer
after_create :check_group_access_buffer
after_update :check_group_access_buffer
association_attributes_ignored :groups
has_many group_through_identifier
has_many :groups, through: group_through_identifier do
# A helper to join the :through table into the result of groups to access :through attributes
#
# @param [String, Array<String>] access Limiting to one or more access verbs. 'full' gets added automatically
#
# @example All access groups
# user.groups.access
# #=> [#<Group id: 1, access="read", ...>, ...]
#
# @example Groups for given access(es) plus 'full'
# user.groups.access('read')
# #=> [#<Group id: 1, access="full", ...>, ...]
#
# @example Groups for given access(es)es plus 'full'
# user.groups.access('read', 'write')
# #=> [#<Group id: 1, access="full", ...>, ...]
#
# @return [ActiveRecord::AssociationRelation<[<Group]>] List of Groups with :through attributes
def access(*access)
table_name = proxy_association.owner.class.group_through.table_name
query = select("groups.*, #{table_name}.*")
return query if access.blank?
access.push('full') if !access.include?('full')
query.where("#{table_name}.access" => access)
end
end
end
# Checks a given Group( ID) for given access(es) for the instance.
# Checks indirect access via Roles if instance has Roles, too.
#
# @example Group ID param
# user.group_access?(1, 'read')
# #=> true
#
# @example Group param
# user.group_access?(group, 'read')
# #=> true
#
# @example Access list
# user.group_access?(group, ['read', 'create'])
# #=> true
#
# @return [Boolean]
def group_access?(group_id, access)
group_id = self.class.ensure_group_id_parameter(group_id)
access = self.class.ensure_group_access_list_parameter(access)
# check direct access
return true if group_through.klass.includes(:group).exists?(
group_through.foreign_key => id,
group_id: group_id,
access: access,
groups: {
active: true
}
)
# check indirect access through Roles if possible
return false if !respond_to?(:role_access?)
role_access?(group_id, access)
end
# Lists the Group IDs the instance has the given access(es) plus 'full' to.
# Adds indirect accessable Group IDs via Roles if instance has Roles, too.
#
# @example Single access
# user.group_ids_access('read')
# #=> [1, 3, ...]
#
# @example Access list
# user.group_ids_access(['read', 'create'])
# #=> [1, 3, ...]
#
# @return [Array<Integer>] Group IDs the instance has the given access(es) to.
def group_ids_access(access)
access = self.class.ensure_group_access_list_parameter(access)
foreign_key = group_through.foreign_key
klass = group_through.klass
# check direct access
ids = klass.includes(:group).where(foreign_key => id, access: access, groups: { active: true }).pluck(:group_id)
ids ||= []
# check indirect access through roles if possible
return ids if !respond_to?(:role_ids)
role_group_ids = RoleGroup.includes(:group).where(role_id: role_ids, access: access, groups: { active: true }).pluck(:group_id)
# combines and removes duplicates
# and returns them in one statement
ids | role_group_ids
end
# Lists Groups the instance has the given access(es) plus 'full' to.
# Adds indirect accessable Groups via Roles if instance has Roles, too.
#
# @example Single access
# user.groups_access('read')
# #=> [#<Group id: 1, access="read", ...>, ...]
#
# @example Access list
# user.groups_access(['read', 'create'])
# #=> [#<Group id: 1, access="read", ...>, ...]
#
# @return [Array<Group>] Groups the instance has the given access(es) to.
def groups_access(access)
group_ids = group_ids_access(access)
Group.where(id: group_ids)
end
# Returns a map of Group name to access
#
# @example
# user.group_names_access_map
# #=> {'Users' => 'full', 'Support' => ['read', 'write']}
#
# @return [Hash<String=>String,Array<String>>] The map of Group name to access
def group_names_access_map
groups_access_map(:name)
end
# Stores a map of Group ID to access. Deletes all other relations.
#
# @example
# user.group_names_access_map = {'Users' => 'full', 'Support' => ['read', 'write']}
# #=> {'Users' => 'full', 'Support' => ['read', 'write']}
#
# @return [Hash<String=>String,Array<String>>] The given map
def group_names_access_map=(name_access_map)
groups_access_map_store(name_access_map) do |group_name|
Group.where(name: group_name).pluck(:id).first
end
end
# Returns a map of Group ID to access
#
# @example
# user.group_ids_access_map
# #=> {1 => 'full', 42 => ['read', 'write']}
#
# @return [Hash<Integer=>String,Array<String>>] The map of Group ID to access
def group_ids_access_map
groups_access_map(:id)
end
# Stores a map of Group ID to access. Deletes all other relations.
#
# @example
# user.group_ids_access_map = {1 => 'full', 42 => ['read', 'write']}
# #=> {1 => 'full', 42 => ['read', 'write']}
#
# @return [Hash<Integer=>String,Array<String>>] The given map
def group_ids_access_map=(id_access_map)
groups_access_map_store(id_access_map)
end
# An alias to .groups class method
def group_through
@group_through ||= self.class.group_through
end
private
def groups_access_map(key)
{}.tap do |hash|
groups.access.where(active: true).pluck(key, :access).each do |entry|
hash[ entry[0] ] ||= []
hash[ entry[0] ].push(entry[1])
end
end
end
def groups_access_map_store(map)
map.each do |group_identifier, accesses|
# use given key as identifier or look it up
# via the given block which returns the identifier
group_id = block_given? ? yield(group_identifier) : group_identifier
if !accesses.is_a?(Array)
accesses = [accesses]
end
accesses.each do |access|
push_group_access_buffer(
group_id: group_id,
access: access
)
Rails.logger.error "TE DEBUG group_access_buffer = #{group_access_buffer.inspect}"
end
end
check_group_access_buffer if id
end
def push_group_access_buffer(entry)
@group_access_buffer ||= []
@group_access_buffer.push(entry)
end
def check_group_access_buffer
return if group_access_buffer.blank?
destroy_group_relations
foreign_key = group_through.foreign_key
entries = group_access_buffer.collect do |entry|
entry[foreign_key] = id
entry
end
group_through.klass.create!(entries)
group_access_buffer = nil
cache_delete
true
end
def destroy_group_relations
group_through.klass.destroy_all(group_through.foreign_key => id)
end
# methods defined here are going to extend the class, not the instance of it
class_methods do
# Lists IDs of instances having the given access(es) to the given Group.
#
# @example Group ID param
# User.group_access_ids(1, 'read')
# #=> [1, 3, ...]
#
# @example Group param
# User.group_access_ids(group, 'read')
# #=> [1, 3, ...]
#
# @example Access list
# User.group_access_ids(group, ['read', 'create'])
# #=> [1, 3, ...]
#
# @return [Array<Integer>]
def group_access_ids(group_id, access)
group_id = ensure_group_id_parameter(group_id)
access = ensure_group_access_list_parameter(access)
# check direct access
ids = group_through.klass.includes(name.downcase).where(group_id: group_id, access: access, table_name => { active: true }).pluck(group_through.foreign_key)
ids ||= []
# check indirect access through roles if possible
return ids if !respond_to?(:role_access_ids)
role_instance_ids = role_access_ids(group_id, access)
# combines and removes duplicates
# and returns them in one statement
ids | role_instance_ids
end
# Lists instances having the given access(es) to the given Group.
#
# @example Group ID param
# User.group_access(1, 'read')
# #=> [#<User id: 1, ...>, ...]
#
# @example Group param
# User.group_access(group, 'read')
# #=> [#<User id: 1, ...>, ...]
#
# @example Access list
# User.group_access(group, ['read', 'create'])
# #=> [#<User id: 1, ...>, ...]
#
# @return [Array<Class>]
def group_access(group_id, access)
instance_ids = group_access_ids(group_id, access)
where(id: instance_ids)
end
# The reflection instance containing the association data
#
# @example
# User.group_through
# #=> <ActiveRecord::Reflection::HasManyReflection:0x007fd2f5785440 @name=:user_groups, ...>
#
# @return [ActiveRecord::Reflection::HasManyReflection] The given map
def group_through
@group_through ||= reflect_on_association(group_through_identifier)
end
# The identifier of the has_many :through relation
#
# @example
# User.group_through_identifier
# #=> :user_groups
#
# @return [Symbol] The relation identifier
def group_through_identifier
"#{name.downcase}_groups".to_sym
end
def ensure_group_id_parameter(group_or_id)
return group_or_id if group_or_id.is_a?(Integer)
group_or_id.id
end
def ensure_group_access_list_parameter(access)
access = [access] if access.is_a?(String)
access.push('full') if !access.include?('full')
access
end
end
end

View file

@ -0,0 +1,75 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
module HasRoles
extend ActiveSupport::Concern
# Checks a given Group( ID) for given access(es) for the instance associated roles.
#
# @example Group ID param
# user.role_access?(1, 'read')
# #=> true
#
# @example Group param
# user.role_access?(group, 'read')
# #=> true
#
# @example Access list
# user.role_access?(group, ['read', 'create'])
# #=> true
#
# @return [Boolean]
def role_access?(group_id, access)
group_id = self.class.ensure_group_id_parameter(group_id)
access = self.class.ensure_group_access_list_parameter(access)
RoleGroup.includes(:group, :role).exists?(
role_id: roles.pluck(:id),
group_id: group_id,
access: access,
groups: {
active: true
},
roles: {
active: true
}
)
end
# methods defined here are going to extend the class, not the instance of it
class_methods do
# Lists IDs of instances having the given access(es) to the given Group through Roles.
#
# @example Group ID param
# User.role_access_ids(1, 'read')
# #=> [1, 3, ...]
#
# @example Group param
# User.role_access_ids(group, 'read')
# #=> [1, 3, ...]
#
# @example Access list
# User.role_access_ids(group, ['read', 'create'])
# #=> [1, 3, ...]
#
# @return [Array<Integer>]
def role_access_ids(group_id, access)
group_id = ensure_group_id_parameter(group_id)
access = ensure_group_access_list_parameter(access)
role_ids = RoleGroup.includes(:role).where(group_id: group_id, access: access, roles: { active: true }).pluck(:role_id)
join_table = reflect_on_association(:roles).join_table
includes(:roles).where(active: true, join_table => { role_id: role_ids }).distinct.pluck(:id)
end
def ensure_group_id_parameter(group_or_id)
return group_or_id if group_or_id.is_a?(Integer)
group_or_id.id
end
def ensure_group_access_list_parameter(access)
access = [access] if access.is_a?(String)
access.push('full') if !access.include?('full')
access
end
end
end

View file

@ -49,7 +49,7 @@ add a new attribute entry for an object
data_type: 'select',
data_option: {
relation: 'Group',
relation_condition: { access: 'rw' },
relation_condition: { access: 'full' },
multiple: false,
null: true,
translate: false,

View file

@ -6,9 +6,8 @@ class Organization < ApplicationModel
include ChecksLatestChangeObserved
include HasHistory
include HasSearchIndexBackend
include Organization::ChecksAccess
load 'organization/permission.rb'
include Organization::Permission
load 'organization/assets.rb'
include Organization::Assets
extend Organization::Search

View file

@ -0,0 +1,48 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Organization
module ChecksAccess
extend ActiveSupport::Concern
# Checks the given access of a given user for an organization.
#
# @param [User] The user that will be checked for given access.
# @param [String] The access that should get checked.
#
# @example
# organization.access?(user, 'read')
# #=> true
#
# @return [Boolean]
def access?(user, access)
# check customer
if user.permissions?('ticket.customer')
# access ok if its own organization
return false if access != 'read'
return false if !user.organization_id
return id == user.organization_id
end
# check agent
return true if user.permissions?('admin')
return true if user.permissions?('ticket.agent')
false
end
# Checks the given access of a given user for an organization and fails with an exception.
#
# @param (see Organization#access?)
#
# @example
# organization.access!(user, 'read')
#
# @raise [NotAuthorized] Gets raised if given user doesn't have the given access.
#
# @return [nil]
def access!(user, access)
return if access?(user, access)
raise Exceptions::NotAuthorized
end
end
end

View file

@ -1,39 +0,0 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Organization
module Permission
=begin
check if user has access to user
user = Organization.find(123)
result = organization.permission(type: 'rw', current_user: User.find(123))
returns
result = true|false
=end
def permission (data)
# check customer
if data[:current_user].permissions?('ticket.customer')
# access ok if its own organization
return false if data[:type] != 'ro'
return false if !data[:current_user].organization_id
return true if id == data[:current_user].organization_id
# no access
return false
end
# check agent
return true if data[:current_user].permissions?('admin')
return true if data[:current_user].permissions?('ticket.agent')
false
end
end
end

View file

@ -105,8 +105,8 @@ class RecentView < ApplicationModel
end
# check permission
return if !record.respond_to?(:permission)
record.permission(current_user: user)
return if !record.respond_to?(:access?)
record.access?(user, 'read')
end
=begin

View file

@ -4,6 +4,10 @@ class Role < ApplicationModel
include HasActivityStreamLog
include ChecksClientNotification
include ChecksLatestChangeObserved
include HasGroups
load 'role/assets.rb'
include Role::Assets
has_and_belongs_to_many :users, after_add: :cache_update, after_remove: :cache_update
has_and_belongs_to_many :permissions, after_add: :cache_update, after_remove: :cache_update, before_add: :validate_agent_limit
@ -13,7 +17,7 @@ class Role < ApplicationModel
before_create :validate_permissions
before_update :validate_permissions
association_attributes_ignored :user_ids
association_attributes_ignored :users
activity_stream_permission 'admin.role'

57
app/models/role/assets.rb Normal file
View file

@ -0,0 +1,57 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Role
module Assets
=begin
get all assets / related models for this roles
role = Role.find(123)
result = role.assets(assets_if_exists)
returns
result = {
:Role => {
123 => role_model_123,
1234 => role_model_1234,
}
}
=end
def assets(data)
app_model = self.class.to_app_model
if !data[ app_model ]
data[ app_model ] = {}
end
if !data[ app_model ][ id ]
local_attributes = attributes_with_association_ids
# set temp. current attributes to assets pool to prevent
# loops, will be updated with lookup attributes later
data[ app_model ][ id ] = local_attributes
local_attributes['group_ids'].each { |group_id, _access|
group = Group.lookup(id: group_id)
next if !group
data = group.assets(data)
}
end
return data if !self['created_by_id'] && !self['updated_by_id']
app_model_user = User.to_app_model
%w(created_by_id updated_by_id).each { |local_user_id|
next if !self[ local_user_id ]
next if data[ app_model_user ] && data[ app_model_user ][ self[ local_user_id ] ]
user = User.lookup(id: self[ local_user_id ])
next if !user
data = user.assets(data)
}
data
end
end
end

13
app/models/role_group.rb Normal file
View file

@ -0,0 +1,13 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class RoleGroup < ApplicationModel
self.table_name = 'roles_groups'
self.primary_keys = :role_id, :group_id, :access
belongs_to :role
belongs_to :group
validates :access, presence: true
def self.ref_key
:role_id
end
end

View file

@ -10,11 +10,10 @@ class Ticket < ApplicationModel
include HasOnlineNotifications
include HasKarmaActivityLog
include HasLinks
include Ticket::ChecksAccess
include Ticket::Escalation
include Ticket::Subject
load 'ticket/permission.rb'
include Ticket::Permission
load 'ticket/assets.rb'
include Ticket::Assets
load 'ticket/search_index.rb'
@ -75,33 +74,9 @@ class Ticket < ApplicationModel
=begin
list of agents in group of ticket
ticket = Ticket.find(123)
result = ticket.agent_of_group
returns
result = [user1, user2, ...]
=end
def agent_of_group
roles = Role.with_permissions('ticket.agent')
role_ids = roles.map(&:id)
Group.find(group_id)
.users.where(active: true)
.joins(:roles)
.where('roles.id' => role_ids, 'roles.active' => true)
.order('users.login')
.uniq()
end
=begin
get user access conditions
conditions = Ticket.access_condition( User.find(1) )
conditions = Ticket.access_condition( User.find(1) , 'full')
returns
@ -109,22 +84,14 @@ returns
=end
def self.access_condition(user)
access_condition = []
def self.access_condition(user, access)
if user.permissions?('ticket.agent')
group_ids = Group.select('groups.id').joins(:users)
.where('groups_users.user_id = ?', user.id)
.where('groups.active = ?', true)
.map(&:id)
access_condition = [ 'group_id IN (?)', group_ids ]
['group_id IN (?)', user.group_ids_access(access)]
elsif !user.organization || ( !user.organization.shared || user.organization.shared == false )
['tickets.customer_id = ?', user.id]
else
access_condition = if !user.organization || ( !user.organization.shared || user.organization.shared == false )
[ 'tickets.customer_id = ?', user.id ]
else
[ '(tickets.customer_id = ? OR tickets.organization_id = ?)', user.id, user.organization.id ]
end
['(tickets.customer_id = ? OR tickets.organization_id = ?)', user.id, user.organization.id]
end
access_condition
end
=begin
@ -393,11 +360,11 @@ returns
get count of tickets and tickets which match on selector
ticket_count, tickets = Ticket.selectors(params[:condition], limit, current_user)
ticket_count, tickets = Ticket.selectors(params[:condition], limit, current_user, 'full')
=end
def self.selectors(selectors, limit = 10, current_user = nil)
def self.selectors(selectors, limit = 10, current_user = nil, access = 'full')
raise 'no selectors given' if !selectors
query, bind_params, tables = selector2sql(selectors, current_user)
return [] if !query
@ -408,7 +375,7 @@ get count of tickets and tickets which match on selector
return [ticket_count, tickets]
end
access_condition = Ticket.access_condition(current_user)
access_condition = Ticket.access_condition(current_user, access)
ticket_count = Ticket.where(access_condition).where(query, *bind_params).joins(tables).count
tickets = Ticket.where(access_condition).where(query, *bind_params).joins(tables).limit(limit)
@ -801,9 +768,9 @@ perform changes on ticket
email = User.lookup(id: owner_id).email
recipients_raw.push(email)
elsif recipient == 'ticket_agents'
agent_of_group.each { |user|
User.group_access(group_id, 'full').order(:login).each do |user|
recipients_raw.push(user.email)
}
end
else
logger.error "Unknown email notification recipient '#{recipient}'"
next

View file

@ -4,6 +4,7 @@ class Ticket::Article < ApplicationModel
include ChecksClientNotification
include HasHistory
include ChecksHtmlSanitized
include Ticket::Article::ChecksAccess
load 'ticket/article/assets.rb'
include Ticket::Article::Assets

View file

@ -0,0 +1,42 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Ticket
class Article
module ChecksAccess
extend ActiveSupport::Concern
# Checks the given access of a given user for a ticket article.
#
# @param [User] The user that will be checked for given access.
# @param [String] The access that should get checked.
#
# @example
# article.access?(user, 'read')
# #=> true
#
# @return [Boolean]
def access?(user, access)
if user.permissions?('ticket.customer')
return false if internal == true
end
ticket = Ticket.lookup(id: ticket_id)
ticket.access?(user, access)
end
# Checks the given access of a given user for a ticket article and fails with an exception.
#
# @param (see Ticket::Article#access?)
#
# @example
# article.access!(user, 'read')
#
# @raise [NotAuthorized] Gets raised if given user doesn't have the given access.
#
# @return [nil]
def access!(user, access)
return if access?(user, access)
raise Exceptions::NotAuthorized
end
end
end
end

View file

@ -0,0 +1,57 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Ticket
module ChecksAccess
extend ActiveSupport::Concern
# Checks the given access of a given user for a ticket.
#
# @param [User] The user that will be checked for given access.
# @param [String] The access that should get checked.
#
# @example
# ticket.access?(user, 'read')
# #=> true
#
# @return [Boolean]
def access?(user, access)
# check customer
if user.permissions?('ticket.customer')
# access ok if its own ticket
return true if customer_id == user.id
# access ok if its organization ticket
if user.organization_id && organization_id
return true if organization_id == user.organization_id
end
# no access
return false
end
# check agent
# access if requestor is owner
return true if owner_id == user.id
# access if requestor is in group
user.group_access?(group.id, access)
end
# Checks the given access of a given user for a ticket and fails with an exception.
#
# @param (see Ticket#access?)
#
# @example
# ticket.access!(user, 'read')
#
# @raise [NotAuthorized] Gets raised if given user doesn't have the given access.
#
# @return [nil]
def access!(user, access)
return if access?(user, access)
raise Exceptions::NotAuthorized
end
end
end

View file

@ -89,7 +89,7 @@ returns
return [] if overviews.blank?
# get only tickets with permissions
access_condition = Ticket.access_condition(user)
access_condition = Ticket.access_condition(user, 'overview')
ticket_attributes = Ticket.new.attributes
list = []

View file

@ -1,45 +0,0 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
module Ticket::Permission
=begin
check if user has access to ticket
ticket = Ticket.find(123)
result = ticket.permission(current_user: User.find(123))
returns
result = true|false
=end
def permission(data)
# check customer
if data[:current_user].permissions?('ticket.customer')
# access ok if its own ticket
return true if customer_id == data[:current_user].id
# access ok if its organization ticket
if data[:current_user].organization_id && organization_id
return true if organization_id == data[:current_user].organization_id
end
# no access
return false
end
# check agent
# access if requestor is owner
return true if owner_id == data[:current_user].id
# access if requestor is in group
data[:current_user].groups.each { |group|
return true if self.group.id == group.id
}
false
end
end

View file

@ -10,6 +10,7 @@ list attributes
article_id: 123,
ticket: ticket_model,
current_user: User.find(123),
)
returns
@ -26,6 +27,8 @@ returns
=end
def self.attributes_to_change(params)
raise 'current_user param needed' if !params[:current_user]
if params[:ticket_id]
params[:ticket] = Ticket.find(params[:ticket_id])
end
@ -45,22 +48,22 @@ returns
if state_type && !state_types.include?(state_type.name)
state_ids.push params[:ticket].state.id
end
state_types.each { |type|
state_types.each do |type|
state_type = Ticket::StateType.find_by(name: type)
next if !state_type
state_type.states.each { |state|
state_type.states.each do |state|
assets = state.assets(assets)
state_ids.push state.id
}
}
end
end
filter[:state_id] = state_ids
# get priorities
priority_ids = []
Ticket::Priority.where(active: true).each { |priority|
Ticket::Priority.where(active: true).each do |priority|
assets = priority.assets(assets)
priority_ids.push priority.id
}
end
filter[:priority_id] = priority_ids
type_ids = []
@ -69,36 +72,45 @@ returns
if params[:ticket].group.email_address_id
types.push 'email'
end
types.each { |type_name|
types.each do |type_name|
type = Ticket::Article::Type.lookup( name: type_name )
if type
type_ids.push type.id
end
}
next if type.blank?
type_ids.push type.id
end
end
filter[:type_id] = type_ids
# get group / user relations
agents = {}
User.with_permissions('ticket.agent').each { |user|
User.with_permissions('ticket.agent').each do |user|
agents[ user.id ] = 1
}
end
dependencies = { group_id: { '' => { owner_id: [] } } }
Group.where(active: true).each { |group|
filter[:group_id] = []
groups = if params[:current_user].permissions?('ticket.agent')
params[:current_user].groups_access('create')
else
Group.where(active: true)
end
groups.each do |group|
filter[:group_id].push group.id
assets = group.assets(assets)
dependencies[:group_id][group.id] = { owner_id: [] }
group.users.each { |user|
User.group_access(group.id, 'full').each do |user|
next if !agents[ user.id ]
assets = user.assets(assets)
dependencies[:group_id][ group.id ][ :owner_id ].push user.id
}
}
end
end
{
assets: assets,
assets: assets,
form_meta: {
filter: filter,
filter: filter,
dependencies: dependencies,
}
}

View file

@ -105,15 +105,9 @@ returns
query_extention['bool']['must'] = []
if current_user.permissions?('ticket.agent')
groups = Group.joins(:users)
.where('groups_users.user_id = ?', current_user.id)
.where('groups.active = ?', true)
group_condition = []
groups.each { |group|
group_condition.push group.id
}
group_ids = current_user.group_ids_access('read')
access_condition = {
'query_string' => { 'default_field' => 'group_id', 'query' => "\"#{group_condition.join('" OR "')}\"" }
'query_string' => { 'default_field' => 'group_id', 'query' => "\"#{group_ids.join('" OR "')}\"" }
}
else
access_condition = if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
@ -151,7 +145,7 @@ returns
end
# fallback do sql query
access_condition = Ticket.access_condition(current_user)
access_condition = Ticket.access_condition(current_user, 'read')
# do query
# - stip out * we already search for *query* -

View file

@ -46,36 +46,8 @@ class Transaction::Notification
# find recipients
recipients_and_channels = []
=begin
# group of agents to work on
if data[:recipient] == 'group'
recipients = ticket.agent_of_group()
# owner
elsif data[:recipient] == 'owner'
if ticket.owner_id != 1
recipients.push ticket.owner
end
# customer
elsif data[:recipient] == 'customer'
if ticket.customer_id != 1
# temporarily disabled
# recipients.push ticket.customer
end
# owner or group of agents to work on
elsif data[:recipient] == 'to_work_on'
if ticket.owner_id != 1
recipients.push ticket.owner
else
recipients = ticket.agent_of_group()
end
end
=end
# loop through all users
possible_recipients = ticket.agent_of_group
possible_recipients = User.group_access(ticket.group_id, 'full').order(:login)
if ticket.owner_id == 1
possible_recipients.push ticket.owner
end

View file

@ -28,9 +28,10 @@ class User < ApplicationModel
include ChecksClientNotification
include HasHistory
include HasSearchIndexBackend
include HasGroups
include HasRoles
include User::ChecksAccess
load 'user/permission.rb'
include User::Permission
load 'user/assets.rb'
include User::Assets
extend User::Search
@ -44,7 +45,6 @@ class User < ApplicationModel
after_update :avatar_for_email_check
after_destroy :avatar_destroy
has_and_belongs_to_many :groups, after_add: :cache_update, after_remove: :cache_update, class_name: 'Group'
has_and_belongs_to_many :roles, after_add: [:cache_update, :check_notifications], after_remove: :cache_update, before_add: :validate_agent_limit, before_remove: :last_admin_check, class_name: 'Role'
has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization'
#has_many :permissions, class_name: 'Permission', through: :roles, class_name: 'Role'

View file

@ -32,7 +32,7 @@ returns
local_attributes = attributes_with_association_ids
# do not transfer crypted pw
local_attributes['password'] = ''
local_attributes.delete('password')
# set temp. current attributes to assets pool to prevent
# loops, will be updated with lookup attributes later
@ -65,7 +65,7 @@ returns
# get groups
if local_attributes['group_ids']
local_attributes['group_ids'].each { |group_id|
local_attributes['group_ids'].each { |group_id, _access|
group = Group.lookup(id: group_id)
next if !group
data = group.assets(data)

View file

@ -0,0 +1,46 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class User
module ChecksAccess
extend ActiveSupport::Concern
# Checks the given access of a given user for another user.
#
# @param [User] The user that will be checked for given access.
# @param [String] The access that should get checked.
#
# @example
# user.access?(user, 'read')
# #=> 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
end
# Checks the given access of a given user for another user and fails with an exception.
#
# @param (see User#access?)
#
# @example
# user.access!(user, 'read')
#
# @raise [NotAuthorized] Gets raised if given user doesn't have the given access.
#
# @return [nil]
def access!(user, access)
return if access?(user, access)
raise Exceptions::NotAuthorized
end
end
end

View file

@ -1,37 +0,0 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class User
module Permission
=begin
check if user has access to user
user = User.find(123)
result = user.permission(type: 'rw', current_user: User.find(123))
returns
result = true|false
=end
def permission (data)
# check customer
if data[:current_user].permissions?('ticket.customer')
# access ok if its own user
return true if id == data[:current_user].id
# no access
return false
end
# check agent
return true if data[:current_user].permissions?('admin.user')
return true if data[:current_user].permissions?('ticket.agent')
false
end
end
end

13
app/models/user_group.rb Normal file
View file

@ -0,0 +1,13 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class UserGroup < ApplicationModel
self.table_name = 'groups_users'
self.primary_keys = :user_id, :group_id, :access
belongs_to :user
belongs_to :group
validates :access, presence: true
def self.ref_key
:user_id
end
end

View file

@ -46,4 +46,8 @@ Rails.application.configure do
# format log
config.log_formatter = Logger::Formatter.new
config.after_initialize do
ActiveRecord::Base.logger = Rails.logger.clone
ActiveRecord::Base.logger.level = Logger::INFO
end
end

View file

@ -161,14 +161,27 @@ class CreateBase < ActiveRecord::Migration
add_foreign_key :roles_users, :roles
create_table :groups_users, id: false do |t|
t.references :user
t.references :group
t.references :user, null: false
t.references :group, null: false
t.string :access, limit: 50, null: false, default: 'full'
end
add_index :groups_users, [:user_id]
add_index :groups_users, [:group_id]
add_index :groups_users, [:access]
add_foreign_key :groups_users, :users
add_foreign_key :groups_users, :groups
create_table :roles_groups, id: false do |t|
t.references :role, null: false
t.references :group, null: false
t.string :access, limit: 50, null: false, default: 'full'
end
add_index :roles_groups, [:role_id]
add_index :roles_groups, [:group_id]
add_index :roles_groups, [:access]
add_foreign_key :roles_groups, :roles
add_foreign_key :roles_groups, :groups
create_table :organizations_users, id: false do |t|
t.references :user
t.references :organization

View file

@ -84,7 +84,7 @@ class FixedAdminUserPermission920 < ActiveRecord::Migration
data_option: {
default: '',
relation: 'Group',
relation_condition: { access: 'rw' },
relation_condition: { access: 'full' },
nulloption: true,
multiple: false,
null: false,

View file

@ -0,0 +1,23 @@
class EnhancedPermissions < ActiveRecord::Migration
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
change_column_null :groups_users, :user_id, false
change_column_null :groups_users, :group_id, false
add_column :groups_users, :access, :string, limit: 50, null: false, default: 'full'
add_index :groups_users, [:access]
UserGroup.connection.schema_cache.clear!
UserGroup.reset_column_information
create_table :roles_groups, id: false do |t|
t.references :role, null: false
t.references :group, null: false
t.string :access, limit: 50, null: false, default: 'full'
end
add_index :roles_groups, [:role_id]
add_index :roles_groups, [:group_id]
add_index :roles_groups, [:access]
end
end

View file

@ -106,7 +106,7 @@ ObjectManager::Attribute.add(
data_option: {
default: '',
relation: 'Group',
relation_condition: { access: 'rw' },
relation_condition: { access: 'full' },
nulloption: true,
multiple: false,
null: false,

View file

@ -1,6 +1,6 @@
class Sessions::Backend::Base
def initialize(user, asset_lookup, client, client_id, ttl = 30)
def initialize(user, asset_lookup, client, client_id, ttl = 10)
@user = user
@client = client
@client_id = client_id

View file

@ -4,7 +4,7 @@ class Sessions::Backend::TicketCreate < Sessions::Backend::Base
# get attributes to update
ticket_create_attributes = Ticket::ScreenOptions.attributes_to_change(
user: @user.id,
current_user: @user,
)
# no data exists

View file

@ -8,7 +8,7 @@ class Stats::TicketChannelDistribution
time_range = 7.days
# get users groups
group_ids = user.groups.map(&:id)
group_ids = user.group_ids_access('full')
# get channels
channels = [

View file

@ -7,7 +7,7 @@ class Stats::TicketEscalation
open_state_ids = Ticket::State.by_category(:open).pluck(:id)
# get users groups
group_ids = user.groups.map(&:id)
group_ids = user.group_ids_access('full')
# owned tickets
own_escalated = Ticket.where(

View file

@ -10,7 +10,7 @@ class Stats::TicketLoadMeasure
count = Ticket.where(owner_id: user.id, state_id: open_state_ids).count
# get total open
total = Ticket.where(group_id: user.groups.map(&:id), state_id: open_state_ids).count
total = Ticket.where(group_id: user.group_ids_access('full'), state_id: open_state_ids).count
average = '-'
state = 'good'

View file

@ -5,7 +5,7 @@ class Stats::TicketWaitingTime
def self.generate(user)
# get users groups
group_ids = user.groups.map(&:id)
group_ids = user.group_ids_access('full')
own_waiting = Ticket.where(
'owner_id = ? AND group_id IN (?) AND updated_at > ?', user.id, group_ids, Time.zone.today

14
spec/factories/role.rb Normal file
View file

@ -0,0 +1,14 @@
FactoryGirl.define do
sequence :test_role_name do |n|
"TestRole#{n}"
end
end
FactoryGirl.define do
factory :role do
name { generate(:test_role_name) }
created_by_id 1
updated_by_id 1
end
end

View file

@ -0,0 +1,523 @@
RSpec.shared_examples 'HasGroups' do
context 'group' do
let(:factory_name) { described_class.name.downcase.to_sym }
let(:instance) { create(factory_name) }
let(:instance_inactive) { create(factory_name, active: false) }
let(:group_full) { create(:group) }
let(:group_read) { create(:group) }
let(:group_inactive) { create(:group, active: false) }
context '.group_through_identifier' do
it 'responds to group_through_identifier' do
expect(described_class).to respond_to(:group_through_identifier)
end
it 'returns a Symbol as identifier' do
expect(described_class.group_through_identifier).to be_a(Symbol)
end
it 'instance responds to group_through_identifier method' do
expect(instance).to respond_to(described_class.group_through_identifier)
end
end
context '.group_through' do
it 'responds to group_through' do
expect(described_class).to respond_to(:group_through)
end
it 'returns the Reflection instance of the has_many :through relation' do
expect(described_class.group_through).to be_a(ActiveRecord::Reflection::HasManyReflection)
end
end
context '#groups' do
it 'responds to groups' do
expect(instance).to respond_to(:groups)
end
context '#groups.access' do
it 'responds to groups.access' do
expect(instance.groups).to respond_to(:access)
end
context 'result' do
before(:each) do
instance.group_names_access_map = {
group_full.name => 'full',
group_read.name => 'read',
group_inactive.name => 'write',
}
end
it 'returns all related Groups' do
expect(instance.groups.access.size).to eq(3)
end
it 'adds join table attribute(s like) access' do
expect(instance.groups.access.first).to respond_to(:access)
end
it 'filters for given access parameter' do
expect(instance.groups.access('read')).to include(group_read)
end
it 'filters for given access list parameter' do
expect(instance.groups.access('read', 'write')).to include(group_read, group_inactive)
end
it 'always includes full access groups' do
expect(instance.groups.access('read')).to include(group_full)
end
end
end
end
context '#group_access?' do
before(:each) do
instance.group_names_access_map = {
group_read.name => 'read',
}
end
it 'responds to group_access?' do
expect(instance).to respond_to(:group_access?)
end
context 'Group ID parameter' do
include_examples '#group_access? call' do
let(:group_parameter) { group_read.id }
end
end
context 'Group parameter' do
include_examples '#group_access? call' do
let(:group_parameter) { group_read }
end
end
it 'prevents inactive Group' do
instance.group_names_access_map = {
group_inactive.name => 'read',
}
expect(instance.group_access?(group_inactive.id, 'read')).to be false
end
end
context '#group_ids_access' do
before(:each) do
instance.group_names_access_map = {
group_read.name => 'read',
}
end
it 'responds to group_ids_access' do
expect(instance).to respond_to(:group_ids_access)
end
it 'lists only active Group IDs' do
instance.group_names_access_map = {
group_read.name => 'read',
group_inactive.name => 'read',
}
result = instance.group_ids_access('read')
expect(result).not_to include(group_inactive.id)
end
context 'single access' do
it 'lists access Group IDs' do
result = instance.group_ids_access('read')
expect(result).to include(group_read.id)
end
it "doesn't list for no access" do
result = instance.group_ids_access('write')
expect(result).not_to include(group_read.id)
end
end
context 'access list' do
it 'lists access Group IDs' do
result = instance.group_ids_access(%w(read write))
expect(result).to include(group_read.id)
end
it "doesn't list for no access" do
result = instance.group_ids_access(%w(write create))
expect(result).not_to include(group_read.id)
end
end
end
context '#groups_access' do
it 'responds to groups_access' do
expect(instance).to respond_to(:groups_access)
end
it 'wraps #group_ids_access' do
expect(instance).to receive(:group_ids_access)
instance.groups_access('read')
end
it 'returns Groups' do
instance.group_names_access_map = {
group_read.name => 'read',
}
result = instance.groups_access('read')
expect(result).to include(group_read)
end
end
context '#group_names_access_map=' do
it 'responds to group_names_access_map=' do
expect(instance).to respond_to(:group_names_access_map=)
end
context 'Group name => access relation storage' do
it 'stores Hash with String values' do
expect do
instance.group_names_access_map = {
group_full.name => 'full',
group_read.name => 'read',
}
end.to change {
described_class.group_through.klass.count
}.by(2)
end
it 'stores Hash with String values' do
expect do
instance.group_names_access_map = {
group_full.name => 'full',
group_read.name => %w(read write),
}
end.to change {
described_class.group_through.klass.count
}.by(3)
end
context 'new instance' do
let(:new_instance) { build(factory_name) }
it "doesn't store directly" do
expect do
new_instance.group_names_access_map = {
group_full.name => 'full',
group_read.name => 'read',
}
end.not_to change {
described_class.group_through.klass.count
}
end
it 'stores after save' do
expect do
new_instance.group_names_access_map = {
group_full.name => 'full',
group_read.name => 'read',
}
new_instance.save
end.to change {
described_class.group_through.klass.count
}.by(2)
end
end
end
end
context '#group_names_access_map' do
it 'responds to group_names_access_map' do
expect(instance).to respond_to(:group_names_access_map)
end
it 'returns instance Group name => access relations as Hash' do
expected = {
group_full.name => ['full'],
group_read.name => ['read'],
}
instance.group_names_access_map = expected
expect(instance.group_names_access_map).to eq(expected)
end
end
context '#group_ids_access_map=' do
it 'responds to group_ids_access_map=' do
expect(instance).to respond_to(:group_ids_access_map=)
end
context 'Group ID => access relation storage' do
it 'stores Hash with String values' do
expect do
instance.group_ids_access_map = {
group_full.id => 'full',
group_read.id => 'read',
}
end.to change {
described_class.group_through.klass.count
}.by(2)
end
it 'stores Hash with String values' do
expect do
instance.group_ids_access_map = {
group_full.id => 'full',
group_read.id => %w(read write),
}
end.to change {
described_class.group_through.klass.count
}.by(3)
end
context 'new instance' do
let(:new_instance) { build(factory_name) }
it "doesn't store directly" do
expect do
new_instance.group_ids_access_map = {
group_full.id => 'full',
group_read.id => 'read',
}
end.not_to change {
described_class.group_through.klass.count
}
end
it 'stores after save' do
expect do
new_instance.group_ids_access_map = {
group_full.id => 'full',
group_read.id => 'read',
}
new_instance.save
end.to change {
described_class.group_through.klass.count
}.by(2)
end
end
end
end
context '#group_ids_access_map' do
it 'responds to group_ids_access_map' do
expect(instance).to respond_to(:group_ids_access_map)
end
it 'returns instance Group ID => access relations as Hash' do
expected = {
group_full.id => ['full'],
group_read.id => ['read'],
}
instance.group_ids_access_map = expected
expect(instance.group_ids_access_map).to eq(expected)
end
end
context '#associations_from_param' do
it 'handles group_ids parameter as group_ids_access_map' do
expected = {
group_full.id => ['full'],
group_read.id => ['read'],
}
instance.associations_from_param(group_ids: expected)
expect(instance.group_ids_access_map).to eq(expected)
end
it 'handles groups parameter as group_names_access_map' do
expected = {
group_full.name => ['full'],
group_read.name => ['read'],
}
instance.associations_from_param(groups: expected)
expect(instance.group_names_access_map).to eq(expected)
end
end
context '#attributes_with_association_ids' do
it 'includes group_ids as group_ids_access_map' do
expected = {
group_full.id => ['full'],
group_read.id => ['read'],
}
instance.group_ids_access_map = expected
result = instance.attributes_with_association_ids
expect(result['group_ids']).to eq(expected)
end
end
context '#attributes_with_association_names' do
it 'includes group_ids as group_ids_access_map' do
expected = {
group_full.id => ['full'],
group_read.id => ['read'],
}
instance.group_ids_access_map = expected
result = instance.attributes_with_association_names
expect(result['group_ids']).to eq(expected)
end
it 'includes groups as group_names_access_map' do
expected = {
group_full.name => ['full'],
group_read.name => ['read'],
}
instance.group_names_access_map = expected
result = instance.attributes_with_association_names
expect(result['groups']).to eq(expected)
end
end
context '.group_access_ids' do
before(:each) do
instance.group_names_access_map = {
group_read.name => 'read',
}
end
it 'responds to group_access_ids' do
expect(described_class).to respond_to(:group_access_ids)
end
it 'lists only active instance IDs' do
instance_inactive.group_names_access_map = {
group_read.name => 'read',
}
result = described_class.group_access_ids(group_read.id, 'read')
expect(result).not_to include(instance_inactive.id)
end
context 'Group ID parameter' do
include_examples '.group_access_ids call' do
let(:group_parameter) { group_read.id }
end
end
context 'Group parameter' do
include_examples '.group_access_ids call' do
let(:group_parameter) { group_read.id }
end
end
end
context '.group_access' do
it 'responds to group_access' do
expect(described_class).to respond_to(:group_access)
end
it 'wraps .group_access_ids' do
expect(described_class).to receive(:group_access_ids)
described_class.group_access(group_read, 'read')
end
it 'returns class instances' do
instance.group_names_access_map = {
group_read.name => 'read',
}
result = described_class.group_access(group_read, 'read')
expect(result).to include(instance)
end
end
it 'destroys relations before instance gets destroyed' do
instance.group_names_access_map = {
group_full.name => 'full',
group_read.name => 'read',
group_inactive.name => 'write',
}
expect do
instance.destroy
end.to change {
described_class.group_through.klass.count
}.by(-3)
end
end
end
RSpec.shared_examples '#group_access? call' do
context 'single access' do
it 'checks positive' do
expect(instance.group_access?(group_parameter, 'read')).to be true
end
it 'checks negative' do
expect(instance.group_access?(group_parameter, 'write')).to be false
end
end
context 'access list' do
it 'checks positive' do
expect(instance.group_access?(group_parameter, %w(read write))).to be true
end
it 'checks negative' do
expect(instance.group_access?(group_parameter, %w(write create))).to be false
end
end
end
RSpec.shared_examples '.group_access_ids call' do
context 'single access' do
it 'lists access IDs' do
expect(described_class.group_access_ids(group_parameter, 'read')).to include(instance.id)
end
it 'excludes non access IDs' do
expect(described_class.group_access_ids(group_parameter, 'write')).not_to include(instance.id)
end
end
context 'access list' do
it 'lists access IDs' do
expect(described_class.group_access_ids(group_parameter, %w(read write))).to include(instance.id)
end
it 'excludes non access IDs' do
expect(described_class.group_access_ids(group_parameter, %w(write create))).not_to include(instance.id)
end
end
end

View file

@ -0,0 +1,268 @@
RSpec.shared_examples 'HasRoles' do
context 'role' do
let(:factory_name) { described_class.name.downcase.to_sym }
let(:instance) { create(factory_name) }
let(:instance_inactive) { create(factory_name, active: false) }
let(:role) { create(:role) }
let(:group_instance) { create(:group) }
let(:group_role) { create(:group) }
let(:group_inactive) { create(:group, active: false) }
context '#role_access?' do
before(:each) do
role.group_names_access_map = {
group_role.name => 'read',
}
instance.roles = [role]
end
it 'responds to role_access?' do
expect(instance).to respond_to(:role_access?)
end
context 'Group ID parameter' do
include_examples '#role_access? call' do
let(:group_parameter) { group_role.id }
end
end
context 'Group parameter' do
include_examples '#role_access? call' do
let(:group_parameter) { group_role }
end
end
it 'prevents inactive Group' do
role.group_names_access_map = {
group_inactive.name => 'read',
}
expect(instance.group_access?(group_inactive.id, 'read')).to be false
end
it 'prevents inactive Role' do
role_inactive = create(:role, active: false)
role_inactive.group_names_access_map = {
group_role.name => 'read',
}
instance.roles = [role_inactive]
expect(instance.group_access?(group_role.id, 'read')).to be false
end
end
context '.role_access_ids' do
before(:each) do
role.group_names_access_map = {
group_role.name => 'read',
}
instance.roles = [role]
end
it 'responds to role_access_ids' do
expect(described_class).to respond_to(:role_access_ids)
end
it 'lists only active instance IDs' do
role.group_names_access_map = {
group_role.name => 'read',
}
result = described_class.group_access_ids(group_role.id, 'read')
expect(result).not_to include(instance_inactive.id)
end
it 'lists only active instance IDs' do
role.group_names_access_map = {
group_role.name => 'read',
}
instance_inactive.roles = [role]
result = described_class.role_access_ids(group_role.id, 'read')
expect(result).not_to include(instance_inactive.id)
end
context 'Group ID parameter' do
include_examples '.role_access_ids call' do
let(:group_parameter) { group_role.id }
end
end
context 'Group parameter' do
include_examples '.role_access_ids call' do
let(:group_parameter) { group_role.id }
end
end
end
context 'group' do
before(:each) do
role.group_names_access_map = {
group_role.name => 'read',
}
instance.roles = [role]
instance.group_names_access_map = {
group_instance.name => 'read',
}
end
context '#group_access?' do
it 'falls back to #role_access?' do
expect(instance).to receive(:role_access?)
instance.group_access?(group_role, 'read')
end
it "doesn't fall back to #role_access? if not needed" do
expect(instance).not_to receive(:role_access?)
instance.group_access?(group_instance, 'read')
end
end
context '#group_ids_access' do
before(:each) do
role.group_names_access_map = {
group_role.name => 'read',
}
instance.roles = [role]
instance.group_names_access_map = {
group_instance.name => 'read',
}
end
it 'lists only active Group IDs' do
role.group_names_access_map = {
group_role.name => 'read',
group_inactive.name => 'read',
}
result = instance.group_ids_access('read')
expect(result).not_to include(group_inactive.id)
end
context 'single access' do
it 'lists access Group IDs' do
result = instance.group_ids_access('read')
expect(result).to include(group_role.id)
end
it "doesn't list for no access" do
result = instance.group_ids_access('write')
expect(result).not_to include(group_role.id)
end
it "doesn't contain duplicate IDs" do
instance.group_names_access_map = {
group_role.name => 'read',
}
result = instance.group_ids_access('read')
expect(result.uniq).to eq(result)
end
end
context 'access list' do
it 'lists access Group IDs' do
result = instance.group_ids_access(%w(read write))
expect(result).to include(group_role.id)
end
it "doesn't list for no access" do
result = instance.group_ids_access(%w(write create))
expect(result).not_to include(group_role.id)
end
it "doesn't contain duplicate IDs" do
instance.group_names_access_map = {
group_role.name => 'read',
}
result = instance.group_ids_access(%w(read create))
expect(result.uniq).to eq(result)
end
end
end
context '.group_access_ids' do
it 'includes the result of .role_access_ids' do
result = described_class.group_access_ids(group_role, 'read')
expect(result).to include(instance.id)
end
it "doesn't contain duplicate IDs" do
instance.group_names_access_map = {
group_role.name => 'read',
}
result = described_class.group_access_ids(group_role, 'read')
expect(result.uniq).to eq(result)
end
end
end
end
end
RSpec.shared_examples '#role_access? call' do
context 'single access' do
it 'checks positive' do
expect(instance.role_access?(group_parameter, 'read')).to be true
end
it 'checks negative' do
expect(instance.role_access?(group_parameter, 'write')).to be false
end
end
context 'access list' do
it 'checks positive' do
expect(instance.role_access?(group_parameter, %w(read write))).to be true
end
it 'checks negative' do
expect(instance.role_access?(group_parameter, %w(write create))).to be false
end
end
end
RSpec.shared_examples '.role_access_ids call' do
context 'single access' do
it 'lists access IDs' do
expect(described_class.role_access_ids(group_parameter, 'read')).to include(instance.id)
end
it 'excludes non access IDs' do
expect(described_class.role_access_ids(group_parameter, 'write')).not_to include(instance.id)
end
end
context 'access list' do
it 'lists access IDs' do
expect(described_class.role_access_ids(group_parameter, %w(read write))).to include(instance.id)
end
it 'excludes non access IDs' do
expect(described_class.role_access_ids(group_parameter, %w(write create))).not_to include(instance.id)
end
end
end

6
spec/models/role_spec.rb Normal file
View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'models/concerns/has_groups_examples'
RSpec.describe Role do
include_examples 'HasGroups'
end

View file

@ -1,6 +1,10 @@
require 'rails_helper'
require 'models/concerns/has_groups_examples'
require 'models/concerns/has_roles_examples'
RSpec.describe User do
include_examples 'HasGroups'
include_examples 'HasRoles'
let(:new_password) { 'N3W54V3PW!' }

View file

@ -59,10 +59,10 @@ class AgentTicketActionLevel0Test < TestCase
)
exists(
displayed: false,
css: '.modal [name="group_ids"]',
css: '.modal .js-groupList',
)
exists(
css: '.modal [name="group_ids"]:checked',
css: '.modal .js-groupListItem[value=full]:checked',
)
click(
css: '.modal button.btn.btn--primary',
@ -105,10 +105,10 @@ class AgentTicketActionLevel0Test < TestCase
exists(
displayed: false,
css: '.modal [name="group_ids"]',
css: '.modal .js-groupList',
)
exists_not(
css: '.modal [name="group_ids"]:checked',
css: '.modal .js-groupListItem[value=full]:checked',
)
# enable agent role
@ -117,10 +117,11 @@ class AgentTicketActionLevel0Test < TestCase
)
exists(
css: '.modal [name="group_ids"]',
displayed: false,
css: '.modal .js-groupList',
)
exists(
css: '.modal [name="group_ids"]:checked',
css: '.modal .js-groupListItem[value=full]:checked',
)
click(
@ -214,8 +215,14 @@ class AgentTicketActionLevel0Test < TestCase
data: {
name: "some group #{rand(999_999_999)}",
member: [
'master@example.com',
'agent1@example.com',
{
login: 'master@example.com',
access: 'full',
},
{
login: 'agent1@example.com',
access: 'full',
},
],
},
)

View file

@ -233,6 +233,7 @@ class AgentTicketAttachmentTest < TestCase
sleep 2
set(browser: browser1, css: '.modal [name="address"]', value: 'some new address')
click(browser: browser1, css: '.modal .js-submit')
modal_disappear(browser: browser1)
# verify is customer has chnaged other browser too
click(browser: browser2, css: '.content.active .tabsSidebar-tab[data-tab="customer"]')
@ -255,6 +256,7 @@ class AgentTicketAttachmentTest < TestCase
click(browser: browser1, css: '.modal .js-option')
click(browser: browser1, css: '.modal .js-submit')
modal_disappear(browser: browser1)
# check if org has changed in second browser
sleep 3

View file

@ -45,7 +45,10 @@ class AgentTicketEmailSignatureTest < TestCase
name: group_name1,
signature: signature_name1,
member: [
'master@example.com'
{
login: 'master@example.com',
access: 'full',
},
],
}
)
@ -54,7 +57,10 @@ class AgentTicketEmailSignatureTest < TestCase
name: group_name2,
signature: signature_name2,
member: [
'master@example.com'
{
login: 'master@example.com',
access: 'full',
},
],
}
)
@ -62,10 +68,14 @@ class AgentTicketEmailSignatureTest < TestCase
data: {
name: group_name3,
member: [
'master@example.com'
{
login: 'master@example.com',
access: 'full',
},
],
}
)
sleep 6
#
# check signature in new ticket

View file

@ -112,7 +112,7 @@ class AgentTicketOverviewLevel0Test < TestCase
css: '.modal input[value="article_count"]',
)
click(css: '.modal .js-submit')
sleep 6
modal_disappear
# check if number and article count is shown
match(
@ -160,7 +160,7 @@ class AgentTicketOverviewLevel0Test < TestCase
css: '.modal input[value="article_count"]',
)
click(css: '.modal .js-submit')
sleep 6
modal_disappear
# check if number and article count is gone
match_not(

View file

@ -264,7 +264,7 @@ class AgentTicketTagTest < TestCase
browser: browser2,
css: '.modal .js-submit',
)
sleep 4
modal_disappear(browser: browser2)
ticket_open_by_search(
browser: browser2,
number: ticket3[:number],
@ -313,7 +313,7 @@ class AgentTicketTagTest < TestCase
browser: browser2,
css: '.modal .js-submit',
)
sleep 4
modal_disappear(browser: browser2)
ticket_open_by_search(
browser: browser2,
number: ticket3[:number],

View file

@ -471,6 +471,7 @@ class ChatTest < TestCase
browser: agent,
css: '.modal .js-submit',
)
modal_disappear(browser: agent)
customer = browser_instance
location(

View file

@ -36,7 +36,7 @@ class FirstStepsTest < TestCase
css: '.modal [name="email"]',
value: "#{agent}@example.com",
)
check(css: '.modal [name="group_ids"]')
check(css: '.modal .js-groupListItem[value=full]')
click(
css: '.modal button.btn.btn--primary',
fast: true,

View file

@ -472,13 +472,14 @@ class TestCase < Test::Unit::TestCase
if params[:position] == 'botton'
position = 'false'
end
screenshot(browser: instance, comment: 'scroll_to_before')
execute(
browser: instance,
js: "\$('#{params[:css]}').get(0).scrollIntoView(#{position})",
mute_log: params[:mute_log]
)
sleep 0.3
screenshot(browser: instance, comment: 'scroll_to_after')
end
=begin
@ -495,7 +496,9 @@ class TestCase < Test::Unit::TestCase
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'modal_ready_before')
sleep 3
screenshot(browser: instance, comment: 'modal_ready_after')
end
=begin
@ -513,11 +516,13 @@ class TestCase < Test::Unit::TestCase
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'modal_disappear_before')
watch_for_disappear(
browser: instance,
css: '.modal',
timeout: params[:timeout] || 8,
)
screenshot(browser: instance, comment: 'modal_disappear_after')
end
=begin
@ -1864,17 +1869,31 @@ wait untill text in selector disabppears
# check if owner selection exists
count = instance.find_elements(css: '.content.active .newTicket select[name="group_id"] option').count
if count.nonzero?
instance.find_elements(css: '.content.active .newTicket select[name="group_id"] option').each { |element|
log('ticket_create invalid group count', text: element.text)
}
end
assert_equal(0, count, 'owner selection should not be showm')
# check count of agents, should be only 3 / - selection + master + agent on init screen
count = instance.find_elements(css: '.content.active .newTicket select[name="owner_id"] option').count
if count != 3
instance.find_elements(css: '.content.active .newTicket select[name="owner_id"] option').each { |element|
log('ticket_create invalid owner count', text: element.text)
}
end
assert_equal(3, count, 'check if owner selection is - selection + master + agent per default')
else
# check count of agents, should be only 1 / - selection on init screen
if !params[:disable_group_check]
count = instance.find_elements(css: '.content.active .newTicket select[name="owner_id"] option').count
if count != 1
instance.find_elements(css: '.content.active .newTicket select[name="owner_id"] option').each { |element|
log('ticket_create invalid owner count', text: element.text)
}
end
assert_equal(1, count, 'check if owner selection is empty per default')
end
select(
@ -2869,7 +2888,10 @@ wait untill text in selector disabppears
name: 'some sla' + random,
signature: 'some signature bame',
member: [
'some_user_login',
{
login: 'some_user_login',
access: 'all',
},
],
},
)
@ -2922,20 +2944,21 @@ wait untill text in selector disabppears
# add member
if data[:member]
data[:member].each { |login|
data[:member].each { |member|
instance.find_elements(css: 'a[href="#manage"]')[0].click
sleep 1
instance.find_elements(css: '.content.active a[href="#manage/users"]')[0].click
sleep 3
element = instance.find_elements(css: '.content.active [name="search"]')[0]
element.clear
element.send_keys(login)
element.send_keys(member[:login])
sleep 3
#instance.find_elements(:css => '.content.active table [data-id]')[0].click
instance.execute_script('$(".content.active table [data-id] td").first().click()')
sleep 3
modal_ready(browser: instance)
#instance.find_elements(:css => 'label:contains(" ' + action[:name] + '")')[0].click
instance.execute_script('$(\'label:contains(" ' + data[:name] + '")\').first().click()')
instance.execute_script('$(".js-groupList tr:contains(\"' + data[:name] + '\") .js-groupListItem[value=' + member[:access] + ']").prop("checked", true)')
screenshot(browser: instance, comment: 'group_create_member')
instance.find_elements(css: '.modal button.js-submit')[0].click
modal_disappear(browser: instance)
}

View file

@ -12,6 +12,18 @@ class UserOrganizationControllerTest < ActionDispatch::IntegrationTest
groups = Group.all
UserInfo.current_user_id = 1
@backup_admin = User.create_or_update(
login: 'backup-admin',
firstname: 'Backup',
lastname: 'Agent',
email: 'backup-admin@example.com',
password: 'adminpw',
active: true,
roles: roles,
groups: groups,
)
@admin = User.create_or_update(
login: 'rest-admin',
firstname: 'Rest',
@ -384,17 +396,29 @@ class UserOrganizationControllerTest < ActionDispatch::IntegrationTest
role = Role.lookup(name: 'Admin')
params = { firstname: "Admin#{firstname}", lastname: 'Admin Last', email: 'new_admin_by_agent@example.com', role_ids: [ role.id ] }
post '/api/v1/users', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert(result)
assert_response(201)
result_user1 = JSON.parse(@response.body)
assert(result_user1)
user = User.find(result_user1['id'])
assert_not(user.role?('Admin'))
assert_not(user.role?('Agent'))
assert(user.role?('Customer'))
assert_equal('new_admin_by_agent@example.com', result_user1['login'])
assert_equal('new_admin_by_agent@example.com', result_user1['email'])
# create user with agent role
role = Role.lookup(name: 'Agent')
params = { firstname: "Agent#{firstname}", lastname: 'Agent Last', email: 'new_agent_by_agent@example.com', role_ids: [ role.id ] }
post '/api/v1/users', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert(result)
assert_response(201)
result_user1 = JSON.parse(@response.body)
assert(result_user1)
user = User.find(result_user1['id'])
assert_not(user.role?('Admin'))
assert_not(user.role?('Agent'))
assert(user.role?('Customer'))
assert_equal('new_agent_by_agent@example.com', result_user1['login'])
assert_equal('new_agent_by_agent@example.com', result_user1['email'])
# create user with customer role
role = Role.lookup(name: 'Customer')

View file

@ -67,15 +67,15 @@ class OtrsImportTest < ActiveSupport::TestCase
assert_equal( true, user1.active )
assert( user1.roles.include?( role_agent ) )
assert( !user1.roles.include?( role_admin ) )
assert( !user1.roles.include?( role_customer ) )
#assert( !user1.roles.include?( role_report ) )
assert_not( user1.roles.include?( role_admin ) )
assert_not( user1.roles.include?( role_customer ) )
#assert_not( user1.roles.include?( role_report ) )
group_dasa = Group.where( name: 'dasa' ).first
group_raw = Group.where( name: 'Raw' ).first
assert( !user1.groups.include?( group_dasa ) )
assert( user1.groups.include?( group_raw ) )
assert_not( user1.groups_access('full').include?( group_dasa ) )
assert( user1.groups_access('full').include?( group_raw ) )
user2 = User.find(3)
assert_equal( 'agent-2 firstname äöüß', user2.firstname )
@ -86,11 +86,11 @@ class OtrsImportTest < ActiveSupport::TestCase
assert( user2.roles.include?( role_agent ) )
assert( user2.roles.include?( role_admin ) )
assert( !user2.roles.include?( role_customer ) )
assert_not( user2.roles.include?( role_customer ) )
#assert( user2.roles.include?( role_report ) )
assert( user2.groups.include?( group_dasa ) )
assert( user2.groups.include?( group_raw ) )
assert( user2.groups_access('full').include?( group_dasa ) )
assert( user2.groups_access('full').include?( group_raw ) )
user3 = User.find(7)
assert_equal( 'invalid', user3.firstname )
@ -100,12 +100,12 @@ class OtrsImportTest < ActiveSupport::TestCase
assert_equal( false, user3.active )
assert( user3.roles.include?( role_agent ) )
assert( !user3.roles.include?( role_admin ) )
assert( !user3.roles.include?( role_customer ) )
assert_not( user3.roles.include?( role_admin ) )
assert_not( user3.roles.include?( role_customer ) )
#assert( user3.roles.include?( role_report ) )
assert( !user3.groups.include?( group_dasa ) )
assert( !user3.groups.include?( group_raw ) )
assert_not( user3.groups_access('full').include?( group_dasa ) )
assert_not( user3.groups_access('full').include?( group_raw ) )
user4 = User.find(8)
assert_equal( 'invalid-temp', user4.firstname )
@ -115,12 +115,12 @@ class OtrsImportTest < ActiveSupport::TestCase
assert_equal( false, user4.active )
assert( user4.roles.include?( role_agent ) )
assert( !user4.roles.include?( role_admin ) )
assert( !user4.roles.include?( role_customer ) )
assert_not( user4.roles.include?( role_admin ) )
assert_not( user4.roles.include?( role_customer ) )
#assert( user4.roles.include?( role_report ) )
assert( !user4.groups.include?( group_dasa ) )
assert( !user4.groups.include?( group_raw ) )
assert_not( user4.groups_access('full').include?( group_dasa ) )
assert_not( user4.groups_access('full').include?( group_raw ) )
end

View file

@ -146,7 +146,7 @@ class ZendeskImportTest < ActiveSupport::TestCase
end
}
assert_equal(check[:roles], user.roles.sort.to_a, "#{user.login} roles")
assert_equal(check[:groups], user.groups.sort.to_a, "#{user.login} groups")
assert_equal(check[:groups], user.groups_access('full').sort.to_a, "#{user.login} groups")
}
end

View file

@ -77,6 +77,8 @@ class ActiveSupport::TestCase
ApplicationHandleInfo.current = 'unknown'
Rails.logger.info '++++NEW++++TEST++++'
travel_back
end
# Add more helper methods to be used by all tests here...

View file

@ -62,7 +62,7 @@ class AssetsTest < ActiveSupport::TestCase
user1 = User.find(user1.id)
attributes = user1.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user1.id]), 'check assets' )
@ -70,7 +70,7 @@ class AssetsTest < ActiveSupport::TestCase
user2 = User.find(user2.id)
attributes = user2.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user2.id]), 'check assets' )
@ -78,7 +78,7 @@ class AssetsTest < ActiveSupport::TestCase
user3 = User.find(user3.id)
attributes = user3.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user3.id]), 'check assets' )
@ -96,7 +96,7 @@ class AssetsTest < ActiveSupport::TestCase
user1_new = User.find(user1.id)
attributes = user1_new.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( !diff(attributes, assets[:User][user1_new.id]), 'check assets' )
@ -110,7 +110,7 @@ class AssetsTest < ActiveSupport::TestCase
user1 = User.find(user1.id)
attributes = user1.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user1.id]), 'check assets' )
@ -118,7 +118,7 @@ class AssetsTest < ActiveSupport::TestCase
user2 = User.find(user2.id)
attributes = user2.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user2.id]), 'check assets' )
@ -126,7 +126,7 @@ class AssetsTest < ActiveSupport::TestCase
user3 = User.find(user3.id)
attributes = user3.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user3.id]), 'check assets' )
@ -209,7 +209,7 @@ class AssetsTest < ActiveSupport::TestCase
admin1 = User.find(admin1.id)
attributes = admin1.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][admin1.id]), 'check assets' )
@ -217,7 +217,7 @@ class AssetsTest < ActiveSupport::TestCase
user1 = User.find(user1.id)
attributes = user1.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user1.id]), 'check assets' )
@ -225,7 +225,7 @@ class AssetsTest < ActiveSupport::TestCase
user2 = User.find(user2.id)
attributes = user2.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user2.id]), 'check assets' )
@ -233,7 +233,7 @@ class AssetsTest < ActiveSupport::TestCase
user3 = User.find(user3.id)
attributes = user3.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert_nil( assets[:User][user3.id], 'check assets' )
@ -251,7 +251,7 @@ class AssetsTest < ActiveSupport::TestCase
attributes = user_new_2.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user_new_2.id]), 'check assets' )
@ -264,7 +264,7 @@ class AssetsTest < ActiveSupport::TestCase
attributes = user_new_2.attributes_with_association_ids
attributes['accounts'] = {}
attributes['password'] = ''
attributes.delete('password')
attributes.delete('token_ids')
attributes.delete('authorization_ids')
assert( diff(attributes, assets[:User][user_new_2.id]), 'check assets' )

View file

@ -64,8 +64,8 @@ class ModelTest < ActiveSupport::TestCase
test 'references test' do
# create base
groups = Group.where( name: 'Users' )
roles = Role.where( name: %w(Agent Admin) )
groups = Group.where(name: 'Users')
roles = Role.where(name: %w(Agent Admin))
agent1 = User.create_or_update(
login: 'model-agent1@example.com',
firstname: 'Model',
@ -104,7 +104,7 @@ class ModelTest < ActiveSupport::TestCase
updated_by_id: agent1.id,
created_by_id: 1,
)
roles = Role.where( name: 'Customer' )
roles = Role.where(name: 'Customer')
customer1 = User.create_or_update(
login: 'model-customer1@example.com',
firstname: 'Model',
@ -153,10 +153,11 @@ class ModelTest < ActiveSupport::TestCase
assert_equal(references1['User']['updated_by_id'], 3)
assert_equal(references1['User']['created_by_id'], 1)
assert_equal(references1['Organization']['updated_by_id'], 1)
assert_equal(references1['UserGroup']['user_id'], 1)
assert(!references1['Group'])
references_total1 = Models.references_total('User', agent1.id)
assert_equal(references_total1, 7)
assert_equal(references_total1, 8)
# verify agent2
references2 = Models.references('User', agent2.id)
@ -164,10 +165,10 @@ class ModelTest < ActiveSupport::TestCase
assert(!references2['User'])
assert(!references2['Organization'])
assert(!references2['Group'])
assert(references2.empty?)
assert_equal(references2['UserGroup']['user_id'], 1)
references_total2 = Models.references_total('User', agent2.id)
assert_equal(references_total2, 0)
assert_equal(references_total2, 1)
Models.merge('User', agent2.id, agent1.id)
@ -177,6 +178,7 @@ class ModelTest < ActiveSupport::TestCase
assert(!references1['User'])
assert(!references1['Organization'])
assert(!references1['Group'])
assert(!references1['UserGroup'])
assert(references1.empty?)
references_total1 = Models.references_total('User', agent1.id)
@ -188,10 +190,11 @@ class ModelTest < ActiveSupport::TestCase
assert_equal(references2['User']['updated_by_id'], 3)
assert_equal(references2['User']['created_by_id'], 1)
assert_equal(references2['Organization']['updated_by_id'], 1)
assert_equal(references2['UserGroup']['user_id'], 2)
assert(!references2['Group'])
references_total2 = Models.references_total('User', agent2.id)
assert_equal(references_total2, 7)
assert_equal(references_total2, 9)
# org

View file

@ -36,7 +36,7 @@ class ObjectCacheTest < ActiveSupport::TestCase
test 'user cache' do
roles = Role.where(name: %w(Agent Admin))
groups = Group.all
groups = Group.all.order(:id)
# be sure that minimum one admin is available
User.create_or_update(
@ -65,7 +65,7 @@ class ObjectCacheTest < ActiveSupport::TestCase
groups: groups,
)
assets = user1.assets({})
assert_equal(user1.group_ids.sort, assets[:User][user1.id]['group_ids'].sort)
assert_equal(user1.group_ids_access_map.sort, assets[:User][user1.id]['group_ids'].sort)
# update group
group1 = groups.first
@ -73,15 +73,16 @@ class ObjectCacheTest < ActiveSupport::TestCase
group1.save
assets = user1.assets({})
assert(assets[:Group][group1.id])
assert_equal(group1.note, assets[:Group][group1.id]['note'])
# update group
assert_equal(user1.group_ids.sort, assets[:User][user1.id]['group_ids'].sort)
assert_equal(user1.group_ids_access_map.sort, assets[:User][user1.id]['group_ids'].sort)
user1.group_ids = []
user1.save
assets = user1.assets({})
assert_equal(user1.group_ids.sort, assets[:User][user1.id]['group_ids'].sort)
assert_equal(user1.group_ids_access_map.sort, assets[:User][user1.id]['group_ids'].sort)
# update role
assert_equal(user1.role_ids.sort, assets[:User][user1.id]['role_ids'].sort)

View file

@ -48,7 +48,7 @@ class RecentViewTest < ActiveSupport::TestCase
ticket2.destroy
list = RecentView.list(user1)
assert(!list[0], 'check if recent view list is empty')
assert_not(list[0], 'check if recent view list is empty')
travel_back
end
@ -61,7 +61,7 @@ class RecentViewTest < ActiveSupport::TestCase
# check if list is empty
list = RecentView.list(user)
assert(!list[0], 'check if recent view list is empty')
assert_not(list[0], 'check if recent view list is empty')
# log entry of not existing record
RecentView.user_log_destroy(user)
@ -69,7 +69,7 @@ class RecentViewTest < ActiveSupport::TestCase
# check if list is empty
list = RecentView.list(user)
assert(!list[0], 'check if recent view list is empty')
assert_not(list[0], 'check if recent view list is empty')
# log entry of not existing model with permission check
RecentView.user_log_destroy(user)
@ -77,7 +77,7 @@ class RecentViewTest < ActiveSupport::TestCase
# check if list is empty
list = RecentView.list(user)
assert(!list[0], 'check if recent view list is empty')
assert_not(list[0], 'check if recent view list is empty')
end
test 'permission tests' do
@ -103,6 +103,12 @@ class RecentViewTest < ActiveSupport::TestCase
updated_by_id: 1,
created_by_id: 1
)
organization2 = Organization.create_if_not_exists(
name: 'Customer Organization Recent View 2',
note: 'some note',
updated_by_id: 1,
created_by_id: 1,
)
# no access for customer
ticket1 = Ticket.create(
@ -122,7 +128,7 @@ class RecentViewTest < ActiveSupport::TestCase
# check if list is empty
list = RecentView.list(customer)
assert(!list[0], 'check if recent view list is empty')
assert_not(list[0], 'check if recent view list is empty')
# log entry of not existing object
RecentView.user_log_destroy(agent)
@ -130,7 +136,7 @@ class RecentViewTest < ActiveSupport::TestCase
# check if list is empty
list = RecentView.list(agent)
assert(!list[0], 'check if recent view list is empty')
assert_not(list[0], 'check if recent view list is empty')
# access for customer via customer id
ticket1 = Ticket.create(
@ -152,27 +158,31 @@ class RecentViewTest < ActiveSupport::TestCase
list = RecentView.list(customer)
assert(list[0]['o_id'], ticket1.id)
assert(list[0]['object'], 'Ticket')
assert(!list[1], 'check if recent view list is empty')
assert_not(list[1], 'check if recent view list is empty')
# log entry
organization = Organization.find(1)
organization1 = Organization.find(1)
RecentView.user_log_destroy(customer)
RecentView.log(organization.class.to_s, organization.id, customer)
RecentView.log(organization1.class.to_s, organization1.id, customer)
RecentView.log(organization2.class.to_s, organization2.id, customer)
# check if list is empty
list = RecentView.list(customer)
assert(!list[0], 'check if recent view list is empty')
assert(list[0], 'check if recent view list is empty')
assert_not(list[1], 'check if recent view list is empty')
# log entry
organization = Organization.find(1)
organization1 = Organization.find(1)
RecentView.user_log_destroy(agent)
RecentView.log(organization.class.to_s, organization.id, agent)
RecentView.log(organization1.class.to_s, organization1.id, agent)
# check if list is empty
list = RecentView.list(agent)
assert(list[0]['o_id'], organization.id)
assert(list[0]['o_id'], organization1.id)
assert(list[0]['object'], 'Organization')
assert(!list[1], 'check if recent view list is empty')
assert_not(list[1], 'check if recent view list is empty')
organization2.destroy
end
end

View file

@ -56,7 +56,7 @@ class SessionBasicTest < ActiveSupport::TestCase
test 'c session create / update' do
# create users
roles = Role.where(name: ['Agent'])
roles = Role.where(name: %w(Agent))
groups = Group.all
UserInfo.current_user_id = 1
@ -148,7 +148,10 @@ class SessionBasicTest < ActiveSupport::TestCase
assert_nil(result2, 'check collections - after touch - recall')
# change collection
group = Group.create(name: "SomeGroup::#{rand(999_999)}", active: true)
group = Group.create(
name: "SomeGroup::#{rand(999_999)}",
active: true
)
travel 4.seconds
# get whole collections
@ -242,31 +245,52 @@ class SessionBasicTest < ActiveSupport::TestCase
test 'c ticket_create' do
UserInfo.current_user_id = 2
user = User.lookup(id: 1)
ticket_create_client1 = Sessions::Backend::TicketCreate.new(user, {}, false, '123-1', 3)
# create users
roles = Role.where(name: %w(Agent))
groups = Group.all
UserInfo.current_user_id = 1
agent1 = User.create_or_update(
login: 'session-agent-1',
firstname: 'Session',
lastname: 'Agent 1',
email: 'session-agent-1@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
agent1.save!
ticket_create_client1 = Sessions::Backend::TicketCreate.new(agent1, {}, false, '123-1', 3)
# get as stream
result1 = ticket_create_client1.push
assert(result1, 'check ticket_create')
sleep 0.6
travel 1.second
# next check should be empty
result1 = ticket_create_client1.push
assert(!result1, 'check ticket_create - recall')
# next check should be empty
sleep 0.6
travel 1.second
result1 = ticket_create_client1.push
assert(!result1, 'check ticket_create - recall 2')
Group.create(name: "SomeTicketCreateGroup::#{rand(999_999)}", active: true)
group = Group.create(name: "SomeTicketCreateGroup::#{rand(999_999)}", active: true)
agent1.groups = Group.all
agent1.save!
# next check should be empty
result1 = ticket_create_client1.push
travel 4.seconds
# get as stream
result1 = ticket_create_client1.push
assert(result1, 'check ticket_create - recall 3')
travel_back
end