Merge branch 'develop' of github.com:martini/zammad into develop

This commit is contained in:
Felix Niklas 2015-08-21 11:14:52 +03:00
commit 89fb464915
74 changed files with 2106 additions and 596 deletions

View file

@ -55,6 +55,7 @@ gem 'net-ldap'
gem 'writeexcel'
gem 'icalendar'
gem 'browser'
# event machine
gem 'eventmachine'

View file

@ -210,7 +210,7 @@ class App.Controller extends Spine.Controller
# console.log('rewrite frontendTimeUpdate', this, $(this).hasClass('escalation'))
ui.frontendTimeUpdateItem(item)
)
App.Interval.set( update, 30000, 'frontendTimeUpdate', 'ui' )
App.Interval.set( update, 61000, 'frontendTimeUpdate', 'ui' )
frontendTimeUpdateItem: (item) =>
timestamp = item.data('time')

View file

@ -401,9 +401,6 @@ class App.GenericHistory extends App.ControllerModal
# enable user popups
@userPopups()
# show frontend times
@delay( @frontendTimeUpdate, 800, 'ui-time-update' )
sortorder: =>
@items = @items.reverse()

View file

@ -274,9 +274,4 @@ class App.ControllerTable extends App.Controller
)
)
time = =>
@frontendTimeUpdate()
@delay(time, 80) # to show time immediately for normal tables
@delay(time, 280) # to show time immediately for tables in modal dialog
table

View file

@ -51,9 +51,6 @@ class App.DashboardActivityStream extends App.Controller
@$('.activity-entries').remove()
@el.append html
# update time
@frontendTimeUpdate()
renderItem: (item) ->
html = $(App.view('dashboard/activity_stream')(
item: item

View file

@ -0,0 +1,50 @@
class Index extends App.Controller
events:
'click [data-type=delete]': 'delete'
constructor: ->
super
return if !@authenticate()
@title 'Devices', true
@load()
@interval(
=>
@load()
62000
)
# fetch data, render view
load: =>
@ajax(
id: 'user_devices'
type: 'GET'
url: @apiPath + '/user_devices'
success: (data) =>
@render(data)
)
render: (data) =>
@html App.view('profile/devices')( devices: data )
delete: (e) =>
e.preventDefault()
id = $(e.target).closest('a').data('device-id')
@ajax(
id: 'user_devices_delete'
type: 'DELETE'
url: "#{@apiPath}/user_devices/#{id}"
processData: true
success: @load
error: @error
)
error: (xhr, status, error) =>
data = JSON.parse( xhr.responseText )
@notify(
type: 'error'
msg: App.i18n.translateContent( data.message )
)
App.Config.set( 'Devices', { prio: 3100, name: 'Devices', parent: '#profile', target: '#profile/devices', controller: Index }, 'NavBarProfile' )

View file

@ -142,15 +142,15 @@ class App.Navigation extends App.ControllerWidgetPermanent
searchFunction = =>
# use cache for search result
if @searchResultCache[@term]
@renderResult( @searchResultCache[@term] )
if @searchResultCache[@query]
@renderResult( @searchResultCache[@query] )
App.Ajax.request(
id: 'search'
type: 'GET'
url: @apiPath + '/search'
data:
term: @term
query: @query
processData: true,
success: (data, status, xhr) =>
@ -158,25 +158,21 @@ class App.Navigation extends App.ControllerWidgetPermanent
App.Collection.loadAssets( data.assets )
# cache search result
@searchResultCache[@term] = data.result
@searchResultCache[@query] = data.result
result = data.result
for area in result
if area.name is 'Ticket'
area.result = []
for id in area.ids
ticket = App.Ticket.find( id )
area.result.push ticket.searchResultAttributes()
else if area.name is 'User'
area.result = []
for id in area.ids
user = App.User.find( id )
area.result.push user.searchResultAttributes()
else if area.name is 'Organization'
area.result = []
for id in area.ids
organization = App.Organization.find( id )
area.result.push organization.searchResultAttributes()
result = {}
for item in data.result
if App[item.type] && App[item.type].find
if !result[item.type]
result[item.type] = []
item_object = App[item.type].find(item.id)
if item_object.searchResultAttributes
item_object_search_attributes = item_object.searchResultAttributes()
result[item.type].push item_object_search_attributes
else
@log 'error', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()"
else
@log 'error', "No such model App.#{item.type}"
@renderResult(result)
@ -219,9 +215,9 @@ class App.Navigation extends App.ControllerWidgetPermanent
removePopovers()
# check if search is needed
term = @$('#global-search').val().trim()
return if !term
@term = term
query = @$('#global-search').val().trim()
return if !query
@query = query
@delay( searchFunction, 220, 'search' )
)
@ -239,11 +235,11 @@ class App.Navigation extends App.ControllerWidgetPermanent
return
# on other keys, show result
term = @$('#global-search').val().trim()
return if !term
return if term is @term
@term = term
@$('.search').toggleClass('filled', !!@term)
query = @$('#global-search').val().trim()
return if !query
return if query is @query
@query = query
@$('.search').toggleClass('filled', !!@query)
@delay( searchFunction, 200, 'search' )
)

View file

@ -42,9 +42,6 @@ class Index extends App.ControllerContent
sessions: data.sessions
)
# show frontend times
@frontendTimeUpdate()
destroy: (e) ->
e.preventDefault()
sessionId = $( e.target ).closest('a').data('session-id')

View file

@ -321,9 +321,6 @@ class Table extends App.Controller
# start organization popups
@organizationPopups()
# show frontend times
@frontendTimeUpdate()
# start bulk action observ
@el.find('.bulkAction').append( @bulk_form() )
if @el.find('.table-overview').find('input[name="bulk"]:checked').length isnt 0

View file

@ -112,9 +112,6 @@ class ArticleViewItem extends App.Controller
article: @article
)
# show frontend times
@frontendTimeUpdate()
# set see more option
@setSeeMore()

View file

@ -12,8 +12,5 @@ class App.TicketZoomMeta extends App.Controller
isCustomer: @isRole('Customer')
)
# show frontend times
@frontendTimeUpdate()
release: =>
App.Ticket.unsubscribe( @subscribeId )

View file

@ -130,9 +130,6 @@ class App.OnlineNotificationWidget extends App.Controller
$( App.view('widget/online_notification_content')(items: items) )
)
# show frontend times
@frontendTimeUpdate()
createContainer: =>
@removeContainer()

View file

@ -144,7 +144,6 @@ class TicketStatsList extends App.Controller
limit: @limit
)
@frontendTimeUpdate()
@ticketPopups()
showAll: (e) =>

View file

@ -107,8 +107,13 @@ class App extends Spine.Controller
# use pretty time for datetime
else if attribute_config.tag is 'datetime'
isHtmlEscape = true
result = "<span class=\"humanTimeFromNow #{attribute_config.class}\" data-time=\"#{result}\">?</span>"
#result = App.i18n.translateTimestamp(result)
timestamp = App.i18n.translateTimestamp(result)
escalation = false
cssClass = attribute_config.class || ''
if cssClass.match 'escalation'
escalation = true
humanTime = App.PrettyDate.humanTime(result, escalation)
result = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{result}\" data-tooltip=\"#{timestamp}\">#{humanTime}</time>"
if !isHtmlEscape && typeof result is 'string'
result = App.Utils.htmlEscape(result)
@ -216,6 +221,14 @@ class App extends Spine.Controller
params.humanFileSize = ( size ) ->
App.Utils.humanFileSize(size)
# define pretty/human time helper
params.humanTime = ( time, escalation = false, cssClass = '') ->
timestamp = App.i18n.translateTimestamp(time)
if escalation
cssClass += ' escalation'
humanTime = App.PrettyDate.humanTime(time, escalation)
"<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{time}\" data-tooltip=\"#{timestamp}\">#{humanTime}</time>"
# define template
JST["app/views/#{name}"](params)
template

View file

@ -58,6 +58,7 @@ class _trackSingleton
)
# log ajax calls
### disabled, only needed for debugging
$(document).bind( 'ajaxComplete', ( e, request, settings ) =>
# do not log ui requests
@ -92,6 +93,7 @@ class _trackSingleton
}
)
)
###
$(window).bind(
'beforeunload'

View file

@ -2,6 +2,7 @@ class App.Auth
@login: (params) ->
App.Log.notice 'Auth', 'login', params
params.data['fingerprint'] = App.Browser.fingerprint()
App.Ajax.request(
id: 'login'
type: 'POST'
@ -21,12 +22,15 @@ class App.Auth
)
@loginCheck: ->
params =
fingerprint: App.Browser.fingerprint()
App.Log.debug 'Auth', 'loginCheck'
App.Ajax.request(
id: 'login_check'
async: false
type: 'GET'
type: 'POST'
url: App.Config.get('api_path') + '/signshow'
data: JSON.stringify(params)
success: (data, status, xhr) =>
# set login (config, session, ...)

View file

@ -47,6 +47,32 @@ class App.Browser
# allow browser
true
@fingerprint: ->
localStorage = window['localStorage']
# read from local storage
if localStorage
fingerprint = localStorage.getItem('fingerprint')
return fingerprint if fingerprint
# detect fingerprint
data = @detection()
resolution = "#{window.screen.availWidth}x#{window.screen.availHeight}/#{window.screen.pixelDepth}"
timezone = new Date().toString().match(/\s\(.+?\)$/)
hashCode = (s) ->
s.split('').reduce(
(a,b) ->
a=((a<<5)-a)+b.charCodeAt(0)
a&a
0
)
fingerprint = hashCode("#{data.browser.name}#{data.browser.major}#{data.os}#{resolution}#{timezone}")
# write to local storage
if localStorage
localStorage.setItem('fingerprint', fingerprint)
fingerprint
@message: (data, version) ->
new App.ControllerModal(
head: 'Browser too old!'

View file

@ -21,6 +21,9 @@ class App.Run extends App.Controller
# create web socket connection
App.WebSocket.connect()
# start frontend time update
@frontendTimeUpdate()
# start navbars
@setupWidget( 'Navigations', 'nav', @el )

View file

@ -115,8 +115,8 @@ class _webSocketSingleton extends App.Controller
# logon websocket
data =
action: 'login'
session:
id: App.Session.get('id')
session_id: App.Config.get('session_id')
fingerprint: App.Browser.fingerprint()
@send(data)
spool: =>

View file

@ -5,7 +5,7 @@
<span class="activity-text">
<%= @item.created_by.displayName() %> <%- @T( @item.type ) %> <%- @T( @item.object_name ) %><% if @item.title: %> (<%= @item.title %>)<% end %>
</span>
<span class="activity-time humanTimeFromNow" data-time="<%- @item.created_at %>">?</span>
<%- @humanTime(@item.created_at, false, 'activity-time') %>
</span>
<span class="activity-icon">
<span class="<%- @item.cssIcon %> icon"></span>

View file

@ -5,7 +5,7 @@
<% for item in @items: %>
<span class="user-popover" data-id="<%= item.created_by.id %>"><%= item.created_by.displayName() %></span> -
<span class="humanTimeFromNow" data-time="<%- item.created_at %>">?</span>
<%- @humanTime(item.created_at) %>
<ul>
<% for content in item.records: %>
<li><%- content %></li>

View file

@ -10,7 +10,7 @@
</div>
<div class="task-text">
<a class="name ticket-popover" data-id="<%- item.id %>" href="#ticket/zoom/<%= item.id %>"><%= item.title %></a>
<div class="time humanTimeFromNow" data-time="<%- item.created_at %>"></div>
<%- @humanTime(item.created_at) %>
</div>
<div class="sidebar-list-item-delete js-delete" data-object="Ticket" data-object-id="<%= item.id %>" data-link-type="<%= type %>" data-type="remove">
<svg class="icon icon-diagonal-cross"><use xlink:href="#icon-diagonal-cross" /></svg>

View file

@ -1,6 +1,7 @@
<% for area, i in @result: %>
<% if i > 0: %> <li class="divider"></li> <% end %>
<% for item in area.result: %>
<% for area, items of @result: %>
<% if done && items.length > 0: %> <li class="divider"></li> <% end %>
<% done = true %>
<% for item in items: %>
<li>
<a href="<%- item.url %>" class="nav-tab nav-tab--search <%= item.class %>" data-id="<%= item.id %>">
<div class="nav-tab-icon">

View file

@ -0,0 +1,30 @@
<div class="page-header">
<div class="page-header-title"><h1><%- @T( 'Devices' ) %></h1></div>
</div>
<form>
<p><%- @T('All computers and browsers that have access to your Zammad appear here.') %></p>
<table class="settings-list">
<thead>
<tr>
<th><%- @T('Name') %></th>
<th><%- @T('Location') %></th>
<th><%- @T('Most recent activity') %></th>
<th><%- @T('Remove') %></th>
</tr>
</thead>
<tbody>
<% for device in @devices: %>
<tr>
<td><%= device.name %></td>
<td><%= device.location %></td>
<td><%- @humanTime(device.updated_at) %></td>
<td><a href="#" data-device-id="<%- device.id %>" data-type="delete" title="<%- @Ti('Delete') %>"<% if device.current: %>disabled<% end %>><svg class="icon-trash"><use xlink:href="#icon-trash"></use></svg></a></td>
</tr>
<% end %>
<tbody>
</table>
</form>

View file

@ -18,8 +18,8 @@
<td><% if session.data.user: %><%= session.data.user.displayName() %><% end %></td>
<td title="<%= session.data.user_agent %>"><%= session.data.user_agent %></td>
<td title="<%= session.data.remote_id %>"><% if session.data.geo && session.data.geo.country_name: %><%= session.data.geo.country_name %> <%= session.data.geo.city_name %><% else: %><%= session.data.remote_id %><% end %></td>
<td><span class="humanTimeFromNow" data-time="<%- session.created_at %>">?</span></td>
<td><span class="humanTimeFromNow" data-time="<%- session.updated_at %>">?</span></td>
<td><%- @humanTime(session.created_at) %></td>
<td><%- @humanTime(session.updated_at) %></td>
<td><a href="#" data-session-id="<%- session.id %>" data-type="delete" title="<%- @Ti('Delete') %>"><svg class="icon-trash" data-type="destroy"><use xlink:href="#icon-trash"></use></svg></a></td>
</tr>
<% end %>

View file

@ -82,4 +82,4 @@
<div class="js-article-actions"></div>
<small class="task-subline zIndex-1"><time class="humanTimeFromNow" data-time="<%- @article.created_at %>">?</time></small>
<small class="task-subline zIndex-1"><%- @humanTime(@article.created_at) %></small>

View file

@ -1,3 +1,3 @@
<small class="task-subline">
<%- @C('ticket_hook') %> <span class="ticket-number"><%- @ticket.number %></span> - <%- @T('created') %> <span class="humanTimeFromNow" data-time="<%- @ticket.created_at %>">?</span> <% if !@isCustomer && @ticket.escalation_time: %> - <%- @T('escalation') %> <span class="humanTimeFromNow escalation" data-time="<%- @ticket.escalation_time %>">?</span><% end %>
<%- @C('ticket_hook') %> <span class="ticket-number"><%- @ticket.number %></span> - <%- @T('created') %> <%- @humanTime(@ticket.created_at) %> <% if !@isCustomer && @ticket.escalation_time: %> - <%- @T('escalation') %> <%- @humanTime(@ticket.escalation_time, true) %><% end %>
</small>

View file

@ -9,7 +9,7 @@
<span class="activity-text">
<%= item.created_by.displayName() %> <%- @T( item.type ) %> <%- @T( item.object_name ) %><% if item.title: %> (<%= item.title %>)<% end %>
</span>
<span class="activity-time humanTimeFromNow" data-time="<%- item.created_at %>">?</span>
<%- @humanTime(item.created_at, false, 'activity-time') %>
</span>
</a>
</div>

View file

@ -9,7 +9,7 @@
</div>
<div class="task-text">
<a class="name ticket-popover" data-id="<%- ticket_id %>" href="<%- ticket.uiUrl() %>"><%= ticket.title %></a>
<div class="time humanTimeFromNow" data-time="<%- ticket.created_at %>"></div>
<%- @humanTime(ticket.created_at, false, 'time') %>
</div>
</li>
<% end %>

View file

@ -27,10 +27,15 @@ body {
flex-direction: column;
}
/* prevent clickable <use xlink:href="#icon-abc"></use> */
use {
pointer-events: none;
}
p {
margin: 14px 0;
color: hsl(60,1%,34%);
&.subtle {
color: hsl(60,1%,74%);
}
@ -47,6 +52,13 @@ p {
a {
outline: none !important;
@extend .u-highlight;
&.is-disabled,
&[disabled] {
pointer-events: none;
cursor: not-allowed;
opacity: .33;
}
}
a.create {

View file

@ -17,7 +17,7 @@ class ApplicationController < ActionController::Base
before_action :set_user, :session_update
before_action :cors_preflight_check
after_action :set_access_control_headers
after_action :user_device_update, :set_access_control_headers
after_action :trigger_events
# For all responses in this controller, return the CORS access control headers.
@ -95,6 +95,60 @@ class ApplicationController < ActionController::Base
session[:user_agent] = request.env['HTTP_USER_AGENT']
end
# user device recent action update
def user_device_update
# return if we are in switch to user mode
return if session[:switched_from_user_id]
# only if user_id exists
return if !session[:user_id]
# only with user device
if !session[:user_device_id]
if params[:fingerprint]
return false if !user_device_log(current_user, 'session')
end
return
end
# check if entry exists / only if write action
return if request.method == 'GET' || request.method == 'OPTIONS'
# only update if needed
return if session[:user_device_update_at] && session[:user_device_update_at] > Time.zone.now - 5.minutes
session[:user_device_update_at] = Time.zone.now
UserDevice.action(
session[:user_device_id],
session[:user_agent],
session[:remote_id],
session[:user_id],
)
end
def user_device_log(user, type)
# return if we are in switch to user mode
return true if session[:switched_from_user_id]
# for sessions we need the fingperprint
if !params[:fingerprint] && type == 'session'
render json: { error: 'Need fingerprint param!' }, status: :unprocessable_entity
return false
end
# add defice if needed
user_device = UserDevice.add(
request.env['HTTP_USER_AGENT'],
request.remote_ip,
user.id,
params[:fingerprint],
type,
)
session[:user_device_id] = user_device.id
end
def authentication_check_only(auth_param)
logger.debug 'authentication_check'
@ -104,7 +158,8 @@ class ApplicationController < ActionController::Base
# already logged in, early exit
if session.id && session[:user_id]
userdata = User.find( session[:user_id] )
userdata = User.find(session[:user_id])
current_user_set(userdata)
return {
@ -114,31 +169,9 @@ class ApplicationController < ActionController::Base
error_message = 'authentication failed'
# check logon session
if params['logon_session']
logon_session = ActiveRecord::SessionStore::Session.where( session_id: params['logon_session'] ).first
# set logon session user to current user
if logon_session
userdata = User.find( logon_session.data[:user_id] )
current_user_set(userdata)
session[:persistent] = true
return {
auth: true
}
end
error_message = 'no valid session, user_id'
end
# check sso
sso_userdata = User.sso(params)
if sso_userdata
current_user_set(sso_userdata)
session[:persistent] = true
return {
@ -154,8 +187,9 @@ class ApplicationController < ActionController::Base
next if !userdata
# set basic auth user to current user
current_user_set(userdata)
user_device_log(userdata, 'basic_auth')
return {
auth: true
}
@ -173,8 +207,8 @@ class ApplicationController < ActionController::Base
next if !userdata
# set token user to current user
current_user_set(userdata)
user_device_log(userdata, 'token_auth')
return {
auth: true
@ -209,9 +243,6 @@ class ApplicationController < ActionController::Base
return false
end
# store current user id into the session
session[:user_id] = current_user.id
# return auth ok
true
end
@ -270,10 +301,14 @@ class ApplicationController < ActionController::Base
config['timezones'][ t.name ] = diff
}
# remember if we can to swich back to user
if session[:switched_from_user_id]
config['switch_back_to_possible'] = true
end
# remember session_id for websocket logon
config['session_id'] = session.id
config
end

View file

@ -3,12 +3,13 @@
class SearchController < ApplicationController
before_action :authentication_check
# GET|POST /api/v1/search
# GET|POST /api/v1/search/:objects
def search_generic
# enable search only for agents and admins
if !current_user.role?(Z_ROLENAME_AGENT) && !current_user.role?(Z_ROLENAME_ADMIN)
# enable search only for users with valid session
if !current_user
response_access_deny
return true
end
@ -19,55 +20,79 @@ class SearchController < ApplicationController
# convert objects string into array of class names
# e.g. user-ticket-another_object = %w( User Ticket AnotherObject )
objects = params[:objects].split('-').map(&:camelize)
search_tickets = objects.delete('Ticket')
if !params[:objects]
objects = Setting.get('models_searchable')
else
objects = params[:objects].split('-').map(&:camelize)
end
# get priorities of result
objects_in_order = []
objects_in_order_hash = {}
objects.each { |object|
preferences = object.constantize.search_preferences(current_user)
next if !preferences
objects_in_order_hash[preferences[:prio]] = object
}
objects_in_order_hash.keys.sort.reverse.each {|prio|
objects_in_order.push objects_in_order_hash[prio]
}
# try search index backend
assets = {}
result = []
if SearchIndexBackend.enabled?
items = SearchIndexBackend.search( query, limit, objects )
items.each { |item|
require item[:type].to_filename
record = Kernel.const_get( item[:type] ).find( item[:id] )
assets = record.assets(assets)
result.push item
# get direct search index based objects
objects_with_direct_search_index = []
objects_without_direct_search_index = []
objects.each { |object|
preferences = object.constantize.search_preferences(current_user)
next if !preferences
if preferences[:direct_search_index]
objects_with_direct_search_index.push object
else
objects_without_direct_search_index.push object
end
}
# do ticket query by Ticket class to handle ticket permissions
if search_tickets
tickets = Ticket.search(
query: query,
limit: limit,
current_user: current_user,
)
tickets.each do |ticket|
assets = ticket.assets(assets)
item = {
id: ticket.id,
type: 'Ticket',
}
# do only one query to index search backend
if !objects_with_direct_search_index.empty?
items = SearchIndexBackend.search( query, limit, objects_with_direct_search_index )
items.each { |item|
require item[:type].to_filename
record = Kernel.const_get( item[:type] ).find( item[:id] )
assets = record.assets(assets)
result.push item
end
}
end
# e. g. do ticket query by Ticket class to handle ticket permissions
objects_without_direct_search_index.each { |object|
object_result = search_generic_backend(object, query, limit, current_user, assets)
if !object_result.empty?
result = result.concat(object_result)
end
}
# sort order by object priority
result_in_order = []
objects_in_order.each { |object|
result.each {|item|
next if item[:type] != object
item[:id] = item[:id].to_i
result_in_order.push item
}
}
result = result_in_order
else
# do query
objects.each { |object|
found_objects = object.constantize.search(
query: query,
limit: limit,
current_user: current_user,
)
found_objects.each do |found_object|
item = {
id: found_object.id,
type: found_object.class.to_s
}
result.push item
assets = found_object.assets(assets)
objects_in_order.each { |object|
object_result = search_generic_backend(object, query, limit, current_user, assets)
if !object_result.empty?
result = result.concat(object_result)
end
}
end
@ -78,84 +103,23 @@ class SearchController < ApplicationController
}
end
# GET /api/v1/search
def search
private
# get params
query = params[:term]
limit = params[:limit] || 10
assets = {}
result = []
objects = %w( Ticket User Organization )
if SearchIndexBackend.enabled?
# to ticket search in serparate call
objects.delete('Ticket')
# to query search index backend (excluse tickets here, see below)
found_objects = {}
items = SearchIndexBackend.search( query, limit, objects )
items.each { |item|
require item[:type].to_filename
record = Kernel.const_get( item[:type] ).find( item[:id] )
assets = record.assets(assets)
found_objects[ item[:type] ] ||= []
found_objects[ item[:type] ].push item[:id]
}
# do ticket query by Ticket class to handle ticket permissions
tickets = Ticket.search(
query: query,
limit: limit,
current_user: current_user,
)
tickets.each do |ticket|
found_objects[ 'Ticket' ] ||= []
found_objects[ 'Ticket' ].push ticket.id
end
# generate whole result
found_objects.each { |object, object_ids|
data = {
name: object,
ids: object_ids,
}
result.push data
}
else
objects.each { |object|
found_objects = object.constantize.search(
query: query,
limit: limit,
current_user: current_user,
)
object_ids = []
found_objects.each do |found_object|
object_ids.push found_object.id
assets = found_object.assets(assets)
end
next if object_ids.empty?
data = {
name: object,
ids: object_ids,
}
result.push data
def search_generic_backend(object, query, limit, current_user, assets)
found_objects = object.constantize.search(
query: query,
limit: limit,
current_user: current_user,
)
result = []
found_objects.each do |found_object|
item = {
id: found_object.id,
type: found_object.class.to_s
}
result.push item
assets = found_object.assets(assets)
end
# return result
render json: {
assets: assets,
result: result,
}
result
end
end

View file

@ -30,6 +30,9 @@ class SessionsController < ApplicationController
# set session user
current_user_set(user)
# log device
return if !user_device_log(user, 'session')
# log new session
user.activity_stream_log( 'session started', user.id, true )
@ -42,18 +45,6 @@ class SessionsController < ApplicationController
# get models
models = SessionHelper.models(user)
# check logon session
logon_session_key = nil
if params['logon_session']
logon_session_key = Digest::MD5.hexdigest( rand(999_999).to_s + Time.zone.now.to_s )
# session = ActiveRecord::SessionStore::Session.create(
# :session_id => logon_session_key,
# :data => {
# :user_id => user['id']
# }
# )
end
# sessions created via this
# controller are persistent
session[:persistent] = true
@ -62,10 +53,10 @@ class SessionsController < ApplicationController
render status: :created,
json: {
session: user,
config: config_frontend,
models: models,
collections: collections,
assets: assets,
logon_session: logon_session_key,
}
end
@ -78,14 +69,6 @@ class SessionsController < ApplicationController
user_id = session[:user_id]
end
# check logon session
if params['logon_session']
session = SessionHelper.get( params['logon_session'] )
if session
user_id = session.data[:user_id]
end
end
if !user_id
# get models
models = SessionHelper.models()
@ -96,7 +79,7 @@ class SessionsController < ApplicationController
models: models,
collections: {
Locale.to_app_model => Locale.where( active: true )
}
},
}
return
end
@ -105,6 +88,9 @@ class SessionsController < ApplicationController
# subsequent requests
user = User.find( user_id )
# log device
return if !user_device_log(user, 'session')
# auto population of default collections
collections, assets = SessionHelper.default_collections(user)
@ -117,10 +103,10 @@ class SessionsController < ApplicationController
# return current session
render json: {
session: user,
config: config_frontend,
models: models,
collections: collections,
assets: assets,
config: config_frontend,
}
end

View file

@ -0,0 +1,44 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class UserDevicesController < ApplicationController
before_action :authentication_check
def index
devices = UserDevice.where(user_id: current_user.id).order('created_at DESC')
devices_full = []
devices.each {|device|
attributes = device.attributes
if device.location_details['city_name'] && !device.location_details['city_name'].empty?
attributes['location'] += ", #{device.location_details['city_name']}"
end
attributes.delete('created_at')
attributes.delete('device_details')
attributes.delete('location_details')
if session[:user_device_id] == device.id
attributes['current'] = true
end
devices_full.push attributes
}
model_index_render_result(devices_full)
end
def destroy
# find device
user_device = UserDevice.find_by(user_id: current_user.id, id: params[:id])
# delete device and session's
if user_device
SessionHelper.list.each {|session|
next if !session.data['user_id']
next if !session.data['user_device_id']
next if session.data['user_device_id'] != user_device.id
SessionHelper.destroy( session.id )
}
user_device.destroy
end
render json: {}, status: :ok
end
end

View file

@ -5,6 +5,33 @@ class Organization
=begin
search organizations preferences
result = Organization.search_preferences(user_model)
returns if user has permissions to search
result = {
prio: 1000,
direct_search_index: true
}
returns if user has no permissions to search
result = false
=end
def search_preferences(current_user)
return false if !current_user.role?('Agent') && !current_user.role?(Z_ROLENAME_ADMIN)
{
prio: 1000,
direct_search_index: true,
}
end
=begin
search organizations
result = Organization.search(
@ -27,7 +54,7 @@ returns
current_user = params[:current_user]
# enable search only for agents and admins
return [] if !current_user.role?('Agent') && !current_user.role?(Z_ROLENAME_ADMIN)
return [] if !search_preferences(current_user)
# try search index backend
if SearchIndexBackend.enabled?

View file

@ -3,6 +3,32 @@ module Ticket::Search
=begin
search tickets preferences
result = Ticket.search_preferences(user_model)
returns if user has permissions to search
result = {
prio: 3000,
direct_search_index: false
}
returns if user has no permissions to search
result = false
=end
def search_preferences(_current_user)
{
prio: 3000,
direct_search_index: false,
}
end
=begin
search tickets via search index
result = Ticket.search(
@ -53,7 +79,7 @@ returns
=end
def search (params)
def search(params)
# get params
query = params[:query]

View file

@ -15,7 +15,11 @@ load translations from online
=end
def self.load
Locale.where(active: true).each {|locale|
locales = Locale.where(active: true)
if Rails.env.test?
locales = Locale.where(active: true, locale: ['en-us', 'de-de'])
end
locales.each {|locale|
url = "https://i18n.zammad.com/api/v1/translations/#{locale.locale}"
if !UserInfo.current_user_id
UserInfo.current_user_id = 1

View file

@ -5,6 +5,33 @@ class User
=begin
search user preferences
result = User.search_preferences(user_model)
returns if user has permissions to search
result = {
prio: 1000,
direct_search_index: true
}
returns if user has no permissions to search
result = false
=end
def search_preferences(current_user)
return false if !current_user.role?('Agent') && !current_user.role?(Z_ROLENAME_ADMIN)
{
prio: 2000,
direct_search_index: true,
}
end
=begin
search user
result = User.search(
@ -27,7 +54,7 @@ returns
current_user = params[:current_user]
# enable search only for agents and admins
return [] if !current_user.role?('Agent') && !current_user.role?(Z_ROLENAME_ADMIN)
return [] if !search_preferences(current_user)
# try search index backend
if SearchIndexBackend.enabled?

191
app/models/user_device.rb Normal file
View file

@ -0,0 +1,191 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class UserDevice < ApplicationModel
store :device_details
store :location_details
validates :name, presence: true
=begin
store device for user
user_device = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'172.0.0.1',
user.id,
'fingerprintABC123',
'session', # session|basic_auth|token_auth|sso
)
=end
def self.add(user_agent, ip, user_id, fingerprint, type)
# get location info
location_details = Service::GeoIp.location(ip)
location = location_details['country_name']
# find device by fingerprint
if fingerprint
user_device = UserDevice.find_by(
user_id: user_id,
fingerprint: fingerprint,
location: location,
)
return action(user_device.id, user_agent, ip, user_id) if user_device
end
# for basic_auth|token_auth search for user agent
if type == 'basic_auth' || type == 'token_auth'
user_device = UserDevice.find_by(
user_id: user_id,
user_agent: user_agent,
location: location,
)
return action(user_device.id, user_agent, ip, user_id) if user_device
end
# get browser details
browser = Browser.new(:ua => user_agent, :accept_language => 'en-us')
browser = {
plattform: browser.platform.to_s.camelize,
name: browser.name,
version: browser.version,
full_version: browser.full_version,
}
# generate device name
name = ''
if browser[:plattform] && browser[:plattform] != 'Other'
name = browser[:plattform]
end
if browser[:name] && browser[:name] != 'Other'
if name && !name.empty?
name += ', '
end
name += browser[:name]
end
# if not identified, use user agent
if !name || name == '' || name == 'Other, Other' || name == 'Other'
name = user_agent
browser[:name] = user_agent
end
# check if exists
user_device = self.find_by(
user_id: user_id,
os: browser[:plattform],
browser: browser[:name],
location: location,
)
if user_device
return action(user_device.id, user_agent, ip, user_id) if user_device
end
# create new device
user_device = self.create(
user_id: user_id,
name: name,
os: browser[:plattform],
browser: browser[:name],
location: location,
device_details: browser,
location_details: location_details,
user_agent: user_agent,
ip: ip,
fingerprint: fingerprint,
)
# send notification if needed
user_devices = UserDevice.where(user_id: user_id).count
if user_devices >= 2
user_device.send_notification
end
user_device
end
=begin
log user device action
UserDevice.action(
user_device_id,
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'172.0.0.1',
user.id,
)
=end
def self.action(user_device_id, user_agent, ip, user_id)
user_device = UserDevice.find(user_device_id)
# update location if needed
if user_device.ip != ip
user_device.ip = ip
location_details = Service::GeoIp.location(ip)
user_device.location_details = location_details
location = location_details['country_name']
user_device.location = location
end
# update attributes
user_device.save
user_device
end
=begin
send new user device info
user_device = UserDevice.find(id)
user_device.send_notification
=end
def send_notification
user = User.find(user_id)
# send mail
data = {}
data[:subject] = '#{config.product_name} signin detected from a new device'
data[:body] = 'Hi #{user.firstname},
it looks like you signed into your #{config.product_name} account using a new device on "#{user_device.created_at}":
Your Location: #{user_device.location}
Your IP: #{user_device.ip}
Your device has been added to your list of known devices, which you can view here:
#{config.http_type}://#{config.fqdn}/#profile/devices
If this wasn\'t you, remove the device, changing your account password, and contacting your administrator. Somebody might have gained unauthorized access to your account.
Your #{config.product_name} Team'
# prepare subject & body
[:subject, :body].each { |key|
data[key.to_sym] = NotificationFactory.build(
locale: user.preferences[:locale],
string: data[key.to_sym],
objects: {
user_device: self,
user: user,
}
)
}
# send notification
NotificationFactory.send(
recipient: user,
subject: data[:subject],
body: data[:body]
)
end
end

View file

@ -0,0 +1,8 @@
# update settings for searchable models
if ActiveRecord::Base.connection.tables.include?('settings')
models_current = Models.searchable.map(&:to_s)
models_config = Setting.get('models_searchable')
if models_config && models_current != models_config
Setting.set('models_searchable', models_current)
end
end

View file

@ -1,7 +0,0 @@
# Be sure to restart your server when you modify this file.
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
Zammad::Application.config.secret_token = '7e2713d027d0cd980171f483a37bff6304f7e994f07f337b6130fec20c2e9c8f8093a9fc70128f13fe9d006f7f785064c8e612e92c6171cb35ba675b626f633d'

View file

@ -9,7 +9,7 @@ Zammad::Application.routes.draw do
# sessions
match api_path + '/signin', to: 'sessions#create', via: :post
match api_path + '/signshow', to: 'sessions#show', via: :get
match api_path + '/signshow', to: 'sessions#show', via: [:get, :post]
match api_path + '/signout', to: 'sessions#destroy', via: [:get, :delete]
match api_path + '/sessions/switch/:id', to: 'sessions#switch_to_user', via: :get

View file

@ -2,8 +2,6 @@ Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
# search
match api_path + '/search', to: 'search#search', via: [:get, :post]
# search_generic
match api_path + '/search', to: 'search#search_generic', via: [:get, :post]
match api_path + '/search/:objects', to: 'search#search_generic', via: [:get, :post]
end

View file

@ -0,0 +1,8 @@
Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
# jobs
match api_path + '/user_devices', to: 'user_devices#index', via: :get
match api_path + '/user_devices/:id', to: 'user_devices#destroy', via: :delete
end

8
config/secrets.yml Normal file
View file

@ -0,0 +1,8 @@
development:
secret_key_base: secret_key_base_is_not_used
test:
secret_key_base: secret_key_base_is_not_used
production:
secret_key_base: secret_key_base_is_not_used

View file

@ -0,0 +1,15 @@
class UpdateModelSearchable < ActiveRecord::Migration
def up
Setting.create_if_not_exists(
title: 'Define searchable models.',
name: 'models_searchable',
area: 'Models::Base',
description: 'Define the models which can be searched for.',
options: {},
state: [],
frontend: false,
)
end
end

View file

@ -0,0 +1,27 @@
class CreateUserDevices < ActiveRecord::Migration
def up
create_table :user_devices do |t|
t.references :user, null: false
t.string :name, limit: 250, null: false
t.string :os, limit: 150, null: true
t.string :browser, limit: 250, null: true
t.string :location, limit: 150, null: true
t.string :device_details, limit: 2500, null: true
t.string :location_details, limit: 2500, null: true
t.string :fingerprint, limit: 160, null: true
t.string :user_agent, limit: 250, null: true
t.string :ip, limit: 160, null: true
t.timestamps
end
add_index :user_devices, [:user_id]
add_index :user_devices, [:os, :browser, :location]
add_index :user_devices, [:fingerprint]
add_index :user_devices, [:updated_at]
add_index :user_devices, [:created_at]
end
def down
drop_table :user_devices
end
end

View file

@ -1187,6 +1187,16 @@ Setting.create_if_not_exists(
frontend: true
)
Setting.create_if_not_exists(
title: 'Define searchable models.',
name: 'models_searchable',
area: 'Models::Base',
description: 'Define the models which can be searched for.',
options: {},
state: [],
frontend: false,
)
Setting.create_if_not_exists(
title: 'Default Screen',
name: 'default_controller',

View file

@ -10,11 +10,11 @@ get list of models
returns
{
'Some::Classname1' => {
Some::Classname1 => {
attributes: ['id', 'name', '...']
reflections: ...model.reflections...
},
'Some::Classname2' => {
Some::Classname2 => {
attributes: ['id', 'name', '...']
reflections: ...model.reflections...
},
@ -35,6 +35,9 @@ returns
model_class = load_adapter(entry)
next if !model_class
next if !model_class.respond_to? :new
next if !model_class.respond_to? :table_name
table_name = model_class.table_name # handle models where not table exists, pending migrations
next if !ActiveRecord::Base.connection.tables.include?(table_name)
model_object = model_class.new
next if !model_object.respond_to? :attributes
all[model_class] = {}
@ -49,6 +52,28 @@ returns
=begin
get list of searchable models
result = Models.searchable
returns
[Model1, Model2, Model3]
=end
def self.searchable
models = []
all.each {|model_class, options|
next if !model_class
next if !model_class.respond_to? :search_preferences
models.push model_class
}
models
end
=begin
get reference list of a models
result = Models.references('User', 2)

View file

@ -150,7 +150,7 @@ return search result
=end
def self.search( query, _limit = 10, index = nil, query_extention = {} )
def self.search( query, limit = 10, index = nil, query_extention = {} )
return [] if !query
url = build_url()
@ -166,7 +166,7 @@ return search result
end
data = {}
data['from'] = 0
data['size'] = 10
data['size'] = limit
data['sort'] =
[
{

View file

@ -37,7 +37,7 @@ module SessionHelper
end
def self.get(id)
ActiveRecord::SessionStore::Session.where( id: id ).first
ActiveRecord::SessionStore::Session.find_by( id: id )
end
def self.list(limit = 10_000)
@ -45,7 +45,7 @@ module SessionHelper
end
def self.destroy(id)
session = ActiveRecord::SessionStore::Session.where( id: id ).first
session = ActiveRecord::SessionStore::Session.find_by( id: id )
return if !session
session.destroy
end

View file

@ -42,9 +42,14 @@ namespace :searchindex do
task :reload, [:opts] => :environment do |_t, _args|
puts 'reload data...'
User.search_index_reload
Organization.search_index_reload
Ticket.search_index_reload
Models.searchable.each {|model_class|
puts " reload #{model_class}"
started_at = Time.zone.now
puts " - started at #{started_at}"
model_class.search_index_reload
took = Time.zone.now - started_at
puts " - took #{took.to_i} seconds"
}
end

View file

@ -1,26 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<html class="dark">
<meta charset="utf-8">
<title>404: Not Found</title>
<link rel="stylesheet" href="/assets/error/style.css">
<body>
<!-- This file lives in public/404.html -->
<div class="dialog">
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
</body>
</html>
<h1>404: Requested Page was not found.</h1>
<div class="error-image" style="background-image: url(/assets/error/error-2.svg)"></div>
<p>Sorry, but the Phoenix is not able to find your page. Try checking the URL for errors.</p>
</html>

View file

@ -1,26 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>The change you wanted was rejected (422)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<html class="dark">
<meta charset="utf-8">
<title>422: Not Found</title>
<link rel="stylesheet" href="/assets/error/style.css">
<body>
<!-- This file lives in public/422.html -->
<div class="dialog">
<h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p>
</div>
</body>
</html>
<h1>422: The change you wanted was rejected.</h1>
<div class="error-image" style="background-image: url(/assets/error/error-1.svg)"></div>
<p>Maybe you tried to change something you didn't have access to.</p>
</html>

View file

@ -1,25 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<html class="dark">
<meta charset="utf-8">
<title>500: Something went wrong</title>
<link rel="stylesheet" href="/assets/error/style.css">
<body>
<!-- This file lives in public/500.html -->
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
</div>
</body>
</html>
<h1>500: We're sorry, but something went wrong.</h1>
<div class="error-image" style="background-image: url(/assets/error/error-2.svg)"></div>
<p>We're sorry, but something went wrong.</p>
</html>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 650" enable-background="new 0 0 750 650"><path fill="#ceb249" d="m402.63 286.83l8.93 173.24-64-218.41z"/><path fill="#f8e192" d="m402.63 286.83l-54.69-.1 121.23 107.4z"/><path fill="#e09b16" d="m406.93 210.14l-40.31 17.79 11.17 89.38 24.31-37.54z"/><path fill="#f3d14f" d="m402.63 286.83l-32.25-53.9-19.02 55.94.23 207.07z"/><path opacity=".15" fill="#fff" d="m402.63 286.83l2.86 50.87-57.55-50.97"/><path fill="#e09b16" d="m406.25 122.5l-31.25-28.75-31.25 28.75-39.16 51.28 40.37 109.22h30.04 30.04l40.37-109.22z"/><path fill="#3f2e20" d="m442.5 227.5l-67.5 20-67.5-20 67.5-15z"/><g fill="#dfe0e1"><ellipse cx="409.5" cy="229.5" rx="22.5" ry="30"/><ellipse cx="345.5" cy="227.5" rx="22.5" ry="30"/></g><ellipse fill="#f8f9fa" cx="375" cy="220.5" rx="22.5" ry="30"/><path fill="#5d3d1b" d="m405.04 283l37.46-55.5-67.5 20-67.5-20 37.46 55.5z"/><path fill="#835c3a" d="m375 247.5l-67.5-20 37.46 55.5h30.04v-35.5"/><path fill="#704c2a" d="m442.5 227.5l-67.5 20v35.5h30.04z"/><g fill="#389cd8"><path d="m552.5 283h-130l-2.5 10h132.5 5v-5c0-2.75-2.25-5-5-5"/><path d="m347.5 283h60v10h-60z"/></g><path fill="#1e5a6e" d="m307.07 248.12l9.67-41.11 56.27 81.16z"/><path fill="#c8431c" d="m343.75 122.5l19.33 71.58 43.17-71.58z"/><path fill="#af2527" d="m375 190.4l-31.25-67.9h62.5"/><path fill="#cb4f83" d="m375 93.75l-31.25 28.75 31.25 36.25 31.25-36.25z"/><path fill="#c8431c" d="m375 93.75l.01 28.75 12.72-59.45z"/><path fill="#af2527" d="m375 93.75l.01 28.75 24.05-59.45z"/><path fill="#3aa3c2" d="m406.25 122.5l39.16 51.28"/><path opacity=".15" fill="#fff" d="m373.01 288.17l-65.94-40.05 3.33-18.51z"/><path fill="#7db3ca" d="m343.75 122.5l-36.68 125.62-2.48-74.34z"/><g fill="#c8431c"><path d="m407.5 303l5-20h-10z"/><path d="m397.5 303l5-20h-10z"/></g><path fill="#cb4f83" d="m412.5 283h-20l4-8h12z"/><path fill="#c8431c" d="m402.5 305l5-24h-10z"/><path opacity=".15" fill="#fff" d="m402.5 305l5-24h-10z"/><g fill="#c8431c"><path d="m342.5 303l-5-20h10z"/><path d="m352.5 303l-5-20h10z"/></g><path fill="#cb4f83" d="m337.5 283h20l-4-8h-12z"/><path fill="#c8431c" d="m347.5 305l-5-24h10z"/><path opacity=".15" fill="#fff" d="m347.5 305l-5-24h10z"/><path fill="#c7e4f1" d="m406.25 122.5l-91.1 197.92 130.26-146.64z"/><path fill="#3aa3c2" d="m315.15 320.42l84.18-132.07 6.92-65.85"/><path opacity=".15" fill="#fff" d="m406.25 122.5l39.16 51.28-46.08 14.57z"/><path fill="#389cd8" d="m197.5 283h125l-4.5 10h-120.5-5v-5c0-2.75 2.25-5 5-5"/><g fill="none" stroke="#a1a4a7" stroke-width="2" stroke-miterlimit="10"><path d="m472.5 247.5v-40"/><path d="m482.5 247.5v-40"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 750 650" enable-background="new 0 0 750 650"><defs><use id="1" opacity=".5" xlink:href="#2"/><path id="2" d="m340 259.82h70v60h-70z"/><clipPath id="0"><use xlink:href="#1"/></clipPath></defs><path fill="#3aa3c2" d="m503.63 133.93l-87.89 145.47-6.4-37.56z"/><path fill="#67878f" d="m483.86 149.06l89.12 56.91-116.42-35.39z"/><path fill="#1e5a6e" d="m483.86 149.06l25.52 102.55-52.82-81.03z"/><path opacity=".15" fill="#fff" d="m475.6 162.83l9.34-9.61 24.44 98.39z"/><path fill="#ceb249" d="m433.17 215.46l60.57-162.56-126.68 189.09z"/><path fill="#f8e192" d="m433.17 215.46l-52.2-16.34 147.9-66.01z"/><path fill="#7db3ca" d="m413.39 244.36l55.36-112.95-36.67 138.54z"/><path fill="#f3d14f" d="m433.17 215.46l-46.96 41.72-5.24-58.06 66.35-198.44z"/><path fill="#3aa3c2" d="m468.75 131.41l98.3-16.15-117.4 55.09z"/><path fill="#c7e4f1" d="m322.42 244.31l146.33-112.9-74.78 152.51z"/><g fill="#dc8c18"><path d="m493.75 271.32h-25v-30z"/><path d="m256 271.32h25v-30z"/></g><path fill="#a27c4c" d="m387.5 614.32l-25 35v-270h25z"/><path fill="#dc8c18" d="m452.5 374.32c0 2.75-2.25 5-5 5h-145c-2.75 0-5-2.25-5-5v-15c0-2.75 2.25-5 5-5h145c2.75 0 5 2.25 5 5v15"/><path fill="#e7b141" d="m379.77 198.83c-2.622-.828-6.913-.828-9.536 0l-85.46 26.988c-2.623.828-4.768 3.756-4.768 6.506v130c0 2.75 2.25 5 5 5h180c2.75 0 5-2.25 5-5v-130c0-2.75-2.146-5.678-4.768-6.506l-85.46-26.988"/><path opacity=".15" fill="#fff" d="m375 218.57l-71.25 22.5v110h142.5v-110z"/><g fill="#dc9118"><path opacity=".2" d="m446.25 241.07v110l18.75 16.25c2.75 0 5-2.25 5-5v-130c0-2.75-2.146-5.678-4.768-6.506l-18.982 15.256"/><path opacity=".2" d="m284.77 225.81c-2.621.828-4.768 3.756-4.768 6.506v130c0 2.75 2.25 5 5 5l18.75-16.25v-110l-18.982-15.256"/><path opacity=".9" d="m303.75 351.07l-18.75 16.25h90v-16.25z"/><path opacity=".65" d="m370.23 198.83l-85.46 26.988 18.982 15.256 71.25-22.5v-20.365c-1.729 0-3.457.207-4.767.621"/><path opacity=".6" d="m446.25 351.07l18.75 16.25h-90v-16.25z"/><path opacity=".9" d="m379.77 198.83l85.46 26.988-18.982 15.256-71.25-22.5v-20.365c1.728 0 3.456.207 4.767.621"/></g><g fill="#36af6a"><path d="m347.5 604.32h-150c-2.75 0-5 2.25-5 5v5h5 150 5v-10h-5"/><path d="m402.5 604.32h150c2.75 0 5 2.25 5 5v5h-5-150-5v-10h5"/></g><path fill="#67878f" d="m422.5 324.82c0 2.75-2.25 5-5 5h-85c-2.75 0-5-2.25-5-5v-70c0-2.75 2.25-5 5-5h85c2.75 0 5 2.25 5 5v70"/><ellipse fill="#444a4f" cx="374.94" cy="340.48" rx="5" ry="6"/><use fill="#7db3ca" xlink:href="#2"/><path opacity=".3" fill="#1e5a6e" d="m422.5 254.82c0-2.75-2.25-5-5-5h-85c-2.75 0-5 2.25-5 5l12.5 5h70l12.5-5m-95 70c0 2.75 2.25 5 5 5h85c2.75 0 5-2.25 5-5l-12.5-5h-70l-12.5 5"/><g opacity=".5"><g clip-path="url(#0)"><path fill="#c8431c" d="m413.81 318.97l6.71-81.51 41.11-41.34z"/><path fill="#e09b16" d="m376.48 236.01l44.04 1.45 28.69-85.39-38.18 23.3z"/><g fill="#c8431c"><path d="m333.56 322.45l15.12-12.04 36.77 9.6z"/><path d="m355.04 298.93l41.81-65.34-3.03 25.14"/></g><path opacity=".15" fill="#fff" d="m313.46 233.81l134.25 4.24-33.9 80.92z"/><path fill="#e09b16" d="m350.77 301.33l43.78 28.12 43.56-67.74-104-25.5z"/><path fill="#cb4f83" d="m410.4 304.77l-15.03 25.52-44.6-28.96-11.33-42.89 49.11 27.8z"/><path fill="#c7e4f1" d="m318.46 247.61l150.29-116.2-80.2 154.83z"/><path fill="#af2527" d="m396.71 322.99l-77.18-16.89 31.24-4.77z"/></g></g><use opacity=".8" fill="#1e5a6e" xlink:href="#2"/><g fill="none" stroke="#fff" stroke-width="2" stroke-miterlimit="10"><path d="m355 284.82h40"/><path d="m355 294.82h40"/></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,80 @@
@font-face {
font-family: 'Fira Sans';
src: url('firasans-regular-webfont.eot');
src: url('firasans-regular-webfont.eot?#iefix') format('embedded-opentype'),
url('firasans-regular-webfont.woff') format('woff'),
url('firasans-regular-webfont.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
html {
font-family: "Fira Sans";
height: 100%;
color: #8c959c;
background: #f8f9fa;
text-align: center;
}
.dark {
background: #444a4f;
color: #919497;
}
body {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 100%;
min-height: 600px;
margin: 0;
padding: 40px 10px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow-x: hidden;
}
h1 {
margin: 0;
color: #444a4f;
max-width: 450px;
}
.dark h1 {
color: white;
}
a {
color: #f1d158;
text-decoration: none;
}
a:visited {
color: #ccb250;
}
ul {
text-align: left;
}
p {
max-width: 400px;
margin: 0 0 20px;
}
.error-image {
height: 650px;
width: 100%;
margin: 30px 0;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}

View file

@ -44,7 +44,7 @@ test( "model ui basic tests", function() {
equal( App.viewPrint( ticket, 'state' ), 'open')
equal( App.viewPrint( ticket, 'state_id' ), 'open')
equal( App.viewPrint( ticket, 'not_existing' ), '-')
equal( App.viewPrint( ticket, 'updated_at' ), "<span class=\"humanTimeFromNow undefined\" data-time=\"2014-11-07T23:43:08.000Z\">?</span>")
equal( App.viewPrint( ticket, 'updated_at' ), '<time class="humanTimeFromNow " data-time="2014-11-07T23:43:08.000Z" data-tooltip="11/07/2014 23:43">11/07/2014</time>')
equal( App.viewPrint( ticket, 'date' ), '02/07/2015')
equal( App.viewPrint( ticket, 'textarea' ), '<div>some new</div><div>line</div>')
@ -55,7 +55,7 @@ test( "model ui basic tests", function() {
equal( App.viewPrint( ticket, 'state' ), 'offen')
equal( App.viewPrint( ticket, 'state_id' ), 'offen')
equal( App.viewPrint( ticket, 'not_existing' ), '-')
equal( App.viewPrint( ticket, 'updated_at' ), "<span class=\"humanTimeFromNow undefined\" data-time=\"2014-11-07T23:43:08.000Z\">?</span>")
equal( App.viewPrint( ticket, 'updated_at' ), '<time class="humanTimeFromNow " data-time="2014-11-07T23:43:08.000Z" data-tooltip="07.11.2014 23:43">07.11.2014</time>')
equal( App.viewPrint( ticket, 'date' ), '07.02.2015')
equal( App.viewPrint( ticket, 'textarea' ), '<div>some new</div><div>line</div>')

View file

@ -91,11 +91,11 @@ test( "table test", function() {
equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
equal( el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '?', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
equal( el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '?', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'false', 'check row 2')
$('#table').append('<hr><h1>table simple II</h1><div id="table2"></div>')
@ -114,11 +114,11 @@ test( "table test", function() {
equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
equal( el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '2 normal', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '?', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'false', 'check row 1')
equal( el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '1 niedrig', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '?', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'true', 'check row 2')
$('#table').append('<hr><h1>table simple III</h1><div id="table3"></div>')
@ -257,7 +257,7 @@ test( "table test", function() {
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(6)').text().trim(), '2 normal', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(7)').text().trim(), 'group 2', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(8)').text().trim(), 'neu', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(9)').text().trim(), '?', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(9)').text().trim(), '11.07.2014', 'check row 1')
equal( el.find('tbody > tr:nth-child(2) > td').length, 9, 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')
@ -269,7 +269,7 @@ test( "table test", function() {
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(7)').text().trim(), 'group 1', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(8)').text().trim(), 'offen', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(9)').text().trim(), '?', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(9)').text().trim(), '10.06.2014', 'check row 2')
equal( el.find('tbody > tr:nth-child(3) > td').length, 9, 'check row 3')
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').val(), '1', 'check row 3')
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').prop('checked'), '', 'check row 3')
@ -281,7 +281,7 @@ test( "table test", function() {
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 3')
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(7)').text().trim(), 'group 2', 'check row 3')
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(8)').text().trim(), 'neu', 'check row 3')
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(9)').text().trim(), '?', 'check row 3')
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(9)').text().trim(), '10.06.2014', 'check row 3')
el.find('input[name="bulk_all"]').click()
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').prop('checked'), true, 'check row 1')
@ -330,7 +330,7 @@ test( "table test", function() {
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '-', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(7)').text().trim(), 'offen', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(8)').text().trim(), '?', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(8)').text().trim(), '10.06.2014', 'check row 2')
equal( el.find('tbody > tr:nth-child(3) > td').length, 1, 'check row 3')
equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1)').text().trim(), 'group 2', 'check row 4')
equal( el.find('tbody > tr:nth-child(4) > td').length, 8, 'check row 4')
@ -343,7 +343,7 @@ test( "table test", function() {
equal( el.find('tbody > tr:nth-child(4) > td:nth-child(5)').text().trim(), '-', 'check row 2')
equal( el.find('tbody > tr:nth-child(4) > td:nth-child(6)').text().trim(), '2 normal', 'check row 4')
equal( el.find('tbody > tr:nth-child(4) > td:nth-child(7)').text().trim(), 'neu', 'check row 4')
equal( el.find('tbody > tr:nth-child(4) > td:nth-child(8)').text().trim(), '?', 'check row 4')
equal( el.find('tbody > tr:nth-child(4) > td:nth-child(8)').text().trim(), '11.07.2014', 'check row 4')
el.find('input[name="bulk"]:eq(1)').click()
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')

View file

@ -12,6 +12,12 @@ require 'sessions'
require 'optparse'
require 'daemons'
# load rails env
dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
Dir.chdir dir
RAILS_ENV = ENV['RAILS_ENV'] || 'development'
require File.join(dir, 'config', 'environment')
# Look for -o with argument, and -I and -D boolean arguments
@options = {
p: 6042,
@ -176,10 +182,23 @@ EventMachine.run {
# get session
if data['action'] == 'login'
@clients[client_id][:session] = data['session']
Sessions.create( client_id, data['session'], { type: 'websocket' } )
# remember ping, send pong back
# get user_id
if data && data['session_id']
session = ActiveRecord::SessionStore::Session.find_by( session_id: data['session_id'] )
end
if session && session.data && session.data['user_id']
new_session_data = { 'id' => session.data['user_id'] }
else
new_session_data = {}
end
@clients[client_id][:session] = new_session_data
Sessions.create( client_id, new_session_data, { type: 'websocket' } )
# remember ping, send pong back
elsif data['action'] == 'ping'
Sessions.touch(client_id)
@clients[client_id][:last_ping] = Time.now.utc.to_i
@ -188,7 +207,7 @@ EventMachine.run {
}
websocket_send(client_id, message)
# broadcast
# broadcast
elsif data['action'] == 'broadcast'
# list all current clients

View file

@ -0,0 +1,103 @@
# encoding: utf-8
require 'test_helper'
class PackagesControllerTest < ActionDispatch::IntegrationTest
setup do
# set accept header
@headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
# create agent
roles = Role.where( name: %w(Admin Agent) )
groups = Group.all
UserInfo.current_user_id = 1
@admin = User.create_or_update(
login: 'packages-admin',
firstname: 'Packages',
lastname: 'Admin',
email: 'packages-admin@example.com',
password: 'adminpw',
active: true,
roles: roles,
groups: groups,
)
# create agent
roles = Role.where( name: 'Agent' )
@agent = User.create_or_update(
login: 'packages-agent@example.com',
firstname: 'Rest',
lastname: 'Agent',
email: 'packages-agent@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
# create customer without org
roles = Role.where( name: 'Customer' )
@customer_without_org = User.create_or_update(
login: 'packages-customer1@example.com',
firstname: 'Packages',
lastname: 'Customer1',
email: 'packages-customer1@example.com',
password: 'customer1pw',
active: true,
roles: roles,
)
end
test 'packages index with nobody' do
# index
get '/api/v1/packages'
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result['packages'])
end
test 'packages index with admin' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('packages-admin@example.com', 'adminpw')
# index
get '/api/v1/packages', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert(result['packages'])
end
test 'packages index with agent' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('packages-agent@example.com', 'adminpw')
# index
get '/api/v1/packages', {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result['packages'])
end
test 'packages index with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('packages-customer1@example.com', 'customer1pw')
# index
get '/api/v1/packages', {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result['packages'])
end
end

View file

@ -0,0 +1,456 @@
# encoding: utf-8
require 'test_helper'
class SearchControllerTest < ActionDispatch::IntegrationTest
setup do
# set accept header
@headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
# create agent
roles = Role.where( name: %w(Admin Agent) )
groups = Group.all
UserInfo.current_user_id = 1
@admin = User.create_or_update(
login: 'search-admin',
firstname: 'Search',
lastname: 'Admin',
email: 'search-admin@example.com',
password: 'adminpw',
active: true,
roles: roles,
groups: groups,
)
# create agent
roles = Role.where( name: 'Agent' )
@agent = User.create_or_update(
login: 'search-agent@example.com',
firstname: 'Search 1234',
lastname: 'Agent',
email: 'search-agent@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
# create customer without org
roles = Role.where( name: 'Customer' )
@customer_without_org = User.create_or_update(
login: 'search-customer1@example.com',
firstname: 'Search',
lastname: 'Customer1',
email: 'search-customer1@example.com',
password: 'customer1pw',
active: true,
roles: roles,
)
# create orgs
@organization = Organization.create_or_update(
name: 'Rest Org',
)
@organization2 = Organization.create_or_update(
name: 'Rest Org #2',
)
@organization3 = Organization.create_or_update(
name: 'Rest Org #3',
)
# create customer with org
@customer_with_org2 = User.create_or_update(
login: 'search-customer2@example.com',
firstname: 'Search',
lastname: 'Customer2',
email: 'search-customer2@example.com',
password: 'customer2pw',
active: true,
roles: roles,
organization_id: @organization.id,
)
@customer_with_org3 = User.create_or_update(
login: 'search-customer3@example.com',
firstname: 'Search',
lastname: 'Customer3',
email: 'search-customer3@example.com',
password: 'customer3pw',
active: true,
roles: roles,
organization_id: @organization.id,
)
Ticket.all.destroy_all
@ticket1 = Ticket.create(
title: 'test 1234-1',
group: Group.lookup( name: 'Users'),
customer_id: @customer_without_org.id,
state: Ticket::State.lookup( name: 'new' ),
priority: Ticket::Priority.lookup( name: '2 normal' ),
updated_by_id: 1,
created_by_id: 1,
)
@article1 = Ticket::Article.create(
ticket_id: @ticket1.id,
from: 'some_sender1@example.com',
to: 'some_recipient1@example.com',
subject: 'some subject1',
message_id: 'some@id',
body: 'some message1',
internal: false,
sender: Ticket::Article::Sender.where(name: 'Customer').first,
type: Ticket::Article::Type.where(name: 'email').first,
updated_by_id: 1,
created_by_id: 1,
)
sleep 1
@ticket2 = Ticket.create(
title: 'test 1234-2',
group: Group.lookup( name: 'Users'),
customer_id: @customer_with_org2.id,
state: Ticket::State.lookup( name: 'new' ),
priority: Ticket::Priority.lookup( name: '2 normal' ),
updated_by_id: 1,
created_by_id: 1,
)
@article2 = Ticket::Article.create(
ticket_id: @ticket2.id,
from: 'some_sender2@example.com',
to: 'some_recipient2@example.com',
subject: 'some subject2',
message_id: 'some@id',
body: 'some message2',
internal: false,
sender: Ticket::Article::Sender.where(name: 'Customer').first,
type: Ticket::Article::Type.where(name: 'email').first,
updated_by_id: 1,
created_by_id: 1,
)
sleep 1
@ticket3 = Ticket.create(
title: 'test 1234-2',
group: Group.lookup( name: 'Users'),
customer_id: @customer_with_org3.id,
state: Ticket::State.lookup( name: 'new' ),
priority: Ticket::Priority.lookup( name: '2 normal' ),
updated_by_id: 1,
created_by_id: 1,
)
@article3 = Ticket::Article.create(
ticket_id: @ticket3.id,
from: 'some_sender3@example.com',
to: 'some_recipient3@example.com',
subject: 'some subject3',
message_id: 'some@id',
body: 'some message3',
internal: false,
sender: Ticket::Article::Sender.where(name: 'Customer').first,
type: Ticket::Article::Type.where(name: 'email').first,
updated_by_id: 1,
created_by_id: 1,
)
# configure es
if ENV['ES_URL']
#fail "ERROR: Need ES_URL - hint ES_URL='http://172.0.0.1:9200'"
Setting.set('es_url', ENV['ES_URL'])
# Setting.set('es_url', 'http://172.0.0.1:9200')
# Setting.set('es_index', 'estest.local_zammad')
# Setting.set('es_user', 'elasticsearch')
# Setting.set('es_password', 'zammad')
# set max attachment size in mb
Setting.set('es_attachment_max_size_in_mb', 1 )
if ENV['ES_INDEX']
#fail "ERROR: Need ES_INDEX - hint ES_INDEX='estest.local_zammad'"
Setting.set('es_index', ENV['ES_INDEX'])
end
# drop/create indexes
#Rake::Task["searchindex:drop"].execute
#Rake::Task["searchindex:create"].execute
system('rake searchindex:rebuild')
# execute background jobs
# execute background jobs
#puts Delayed::Job.all.inspect
Delayed::Worker.new.work_off
sleep 6
end
end
test 'settings index with nobody' do
params = {
query: 'test 1234',
limit: 2,
}
post '/api/v1/search/ticket', params.to_json, @headers
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result.empty?)
post '/api/v1/search/user', params.to_json, @headers
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result.empty?)
post '/api/v1/search', params.to_json, @headers
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result.empty?)
end
test 'settings index with admin' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('search-admin@example.com', 'adminpw')
params = {
query: '1234*',
limit: 1,
}
post '/api/v1/search', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('User', result['result'][1]['type'])
assert_equal(@agent.id, result['result'][1]['id'])
assert_not(result['result'][2])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('Ticket', result['result'][1]['type'])
assert_equal(@ticket2.id, result['result'][1]['id'])
assert_equal('Ticket', result['result'][2]['type'])
assert_equal(@ticket1.id, result['result'][2]['id'])
assert_equal('User', result['result'][3]['type'])
assert_equal(@agent.id, result['result'][3]['id'])
assert_not(result['result'][4])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/ticket', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('Ticket', result['result'][1]['type'])
assert_equal(@ticket2.id, result['result'][1]['id'])
assert_equal('Ticket', result['result'][2]['type'])
assert_equal(@ticket1.id, result['result'][2]['id'])
assert_not(result['result'][3])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/user', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal('User', result['result'][0]['type'])
assert_equal(@agent.id, result['result'][0]['id'])
assert_not(result['result'][1])
end
test 'settings index with agent' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('search-agent@example.com', 'agentpw')
params = {
query: '1234*',
limit: 1,
}
post '/api/v1/search', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('User', result['result'][1]['type'])
assert_equal(@agent.id, result['result'][1]['id'])
assert_not(result['result'][2])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('Ticket', result['result'][1]['type'])
assert_equal(@ticket2.id, result['result'][1]['id'])
assert_equal('Ticket', result['result'][2]['type'])
assert_equal(@ticket1.id, result['result'][2]['id'])
assert_equal('User', result['result'][3]['type'])
assert_equal(@agent.id, result['result'][3]['id'])
assert_not(result['result'][4])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/ticket', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('Ticket', result['result'][1]['type'])
assert_equal(@ticket2.id, result['result'][1]['id'])
assert_equal('Ticket', result['result'][2]['type'])
assert_equal(@ticket1.id, result['result'][2]['id'])
assert_not(result['result'][3])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/user', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal('User', result['result'][0]['type'])
assert_equal(@agent.id, result['result'][0]['id'])
assert_not(result['result'][1])
end
test 'settings index with customer 1' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('search-customer1@example.com', 'customer1pw')
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket1.id, result['result'][0]['id'])
assert_not(result['result'][1])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/ticket', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket1.id, result['result'][0]['id'])
assert_not(result['result'][1])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/user', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_not(result['result'][0])
end
test 'settings index with customer 2' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('search-customer2@example.com', 'customer2pw')
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('Ticket', result['result'][1]['type'])
assert_equal(@ticket2.id, result['result'][1]['id'])
assert_not(result['result'][2])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/ticket', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert(result)
assert_equal('Ticket', result['result'][0]['type'])
assert_equal(@ticket3.id, result['result'][0]['id'])
assert_equal('Ticket', result['result'][1]['type'])
assert_equal(@ticket2.id, result['result'][1]['id'])
assert_not(result['result'][2])
params = {
query: '1234*',
limit: 10,
}
post '/api/v1/search/user', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_not(result['result'][0])
end
end

View file

@ -0,0 +1,103 @@
# encoding: utf-8
require 'test_helper'
class SettingsControllerTest < ActionDispatch::IntegrationTest
setup do
# set accept header
@headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
# create agent
roles = Role.where( name: %w(Admin Agent) )
groups = Group.all
UserInfo.current_user_id = 1
@admin = User.create_or_update(
login: 'packages-admin',
firstname: 'Packages',
lastname: 'Admin',
email: 'packages-admin@example.com',
password: 'adminpw',
active: true,
roles: roles,
groups: groups,
)
# create agent
roles = Role.where( name: 'Agent' )
@agent = User.create_or_update(
login: 'packages-agent@example.com',
firstname: 'Rest',
lastname: 'Agent',
email: 'packages-agent@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
# create customer without org
roles = Role.where( name: 'Customer' )
@customer_without_org = User.create_or_update(
login: 'packages-customer1@example.com',
firstname: 'Packages',
lastname: 'Customer1',
email: 'packages-customer1@example.com',
password: 'customer1pw',
active: true,
roles: roles,
)
end
test 'settings index with nobody' do
# index
get '/api/v1/settings'
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result['settings'])
end
test 'settings index with admin' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('packages-admin@example.com', 'adminpw')
# index
get '/api/v1/settings', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Array, result.class)
assert(result)
end
test 'settings index with agent' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('packages-agent@example.com', 'adminpw')
# index
get '/api/v1/settings', {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result['settings'])
end
test 'settings index with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('packages-customer1@example.com', 'customer1pw')
# index
get '/api/v1/settings', {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_not(result['settings'])
end
end

View file

@ -0,0 +1,325 @@
# encoding: utf-8
require 'test_helper'
class UserOrganizationControllerTest < ActionDispatch::IntegrationTest
setup do
# set accept header
@headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
# create agent
roles = Role.where( name: %w(Admin Agent) )
groups = Group.all
UserInfo.current_user_id = 1
@admin = User.create_or_update(
login: 'rest-admin',
firstname: 'Rest',
lastname: 'Agent',
email: 'rest-admin@example.com',
password: 'adminpw',
active: true,
roles: roles,
groups: groups,
)
# create agent
roles = Role.where( name: 'Agent' )
@agent = User.create_or_update(
login: 'rest-agent@example.com',
firstname: 'Rest',
lastname: 'Agent',
email: 'rest-agent@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
# create customer without org
roles = Role.where( name: 'Customer' )
@customer_without_org = User.create_or_update(
login: 'rest-customer1@example.com',
firstname: 'Rest',
lastname: 'Customer1',
email: 'rest-customer1@example.com',
password: 'customer1pw',
active: true,
roles: roles,
)
# create orgs
@organization = Organization.create_or_update(
name: 'Rest Org',
)
@organization2 = Organization.create_or_update(
name: 'Rest Org #2',
)
@organization3 = Organization.create_or_update(
name: 'Rest Org #3',
)
# create customer with org
@customer_with_org = User.create_or_update(
login: 'rest-customer2@example.com',
firstname: 'Rest',
lastname: 'Customer2',
email: 'rest-customer2@example.com',
password: 'customer2pw',
active: true,
roles: roles,
organization_id: @organization.id,
)
end
test 'user create tests - no user' do
# create user with disabled feature
Setting.set('user_create_account', false)
post '/api/v1/users', {}, @headers
assert_response(422)
result = JSON.parse(@response.body)
assert(result['error'])
assert_equal('Feature not enabled!', result['error'])
# already existing user with enabled feature
Setting.set('user_create_account', true)
params = { email: 'rest-customer1@example.com' }
post '/api/v1/users', params.to_json, @headers
assert_response(422)
result = JSON.parse(@response.body)
assert(result['error'])
assert_equal('User already exists!', result['error'])
# create user with enabled feature
params = { firstname: 'Me First', lastname: 'Me Last', email: 'new_here@example.com' }
post '/api/v1/users', params.to_json, @headers
assert_response(201)
result = JSON.parse(@response.body)
assert(result)
assert_equal('Me First', result['firstname'])
assert_equal('Me Last', result['lastname'])
assert_equal('new_here@example.com', result['login'])
assert_equal('new_here@example.com', result['email'])
# no user
get '/api/v1/users', {}, @headers
assert_response(401)
result = JSON.parse(@response.body)
assert_equal('authentication failed', result['error'])
end
test 'auth tests - not existing user' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('not_existing@example.com', 'adminpw')
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal('authentication failed', result['error'])
end
test 'auth tests - username auth, wrong pw' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin', 'not_existing')
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal('authentication failed', result['error'])
end
test 'auth tests - email auth, wrong pw' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'not_existing')
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal('authentication failed', result['error'])
end
test 'auth tests - username auth' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin', 'adminpw')
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert(result)
end
test 'auth tests - email auth' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw')
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert(result)
end
test 'user index with admin' do
# email auth
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw')
# index
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert(result)
# index
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert(result)
assert_equal(result.class, Array)
assert(result.length >= 3)
# show/:id
get "/api/v1/users/#{@agent.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert(result)
assert_equal(result.class, Hash)
assert_equal(result['email'], 'rest-agent@example.com')
get "/api/v1/users/#{@customer_without_org.id}", {}, 'Authorization' => credentials
assert_response(200)
result = JSON.parse(@response.body)
assert(result)
assert_equal(result.class, Hash)
assert_equal(result['email'], 'rest-customer1@example.com')
end
test 'user index with customer1' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-customer1@example.com', 'customer1pw')
# index
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Array)
assert_equal(result.length, 1)
# show/:id
get "/api/v1/users/#{@customer_without_org.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_equal(result['email'], 'rest-customer1@example.com')
get "/api/v1/users/#{@customer_with_org.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert(result.empty?)
end
test 'user index with customer2' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-customer2@example.com', 'customer2pw')
# index
get '/api/v1/users', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Array)
assert_equal(result.length, 1)
# show/:id
get "/api/v1/users/#{@customer_with_org.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert_equal(result['email'], 'rest-customer2@example.com')
get "/api/v1/users/#{@customer_without_org.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(401)
#puts @response.body
result = JSON.parse(@response.body)
assert_equal(result.class, Hash)
assert(result.empty?)
end
test 'organization index with agent' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-agent@example.com', 'agentpw')
# index
get '/api/v1/organizations', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Array)
assert(result.length >= 3)
# show/:id
get "/api/v1/organizations/#{@organization.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal( result.class, Hash)
assert_equal( result['name'], 'Rest Org')
get "/api/v1/organizations/#{@organization2.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal( result.class, Hash)
assert_equal( result['name'], 'Rest Org #2')
end
test 'organization index with customer1' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-customer1@example.com', 'customer1pw')
# index
get '/api/v1/organizations', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Array)
assert_equal(result.length, 0)
# show/:id
get "/api/v1/organizations/#{@organization.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal( result.class, Hash)
assert_equal( result['name'], nil)
get "/api/v1/organizations/#{@organization2.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal( result.class, Hash)
assert_equal( result['name'], nil)
end
test 'organization index with customer2' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-customer2@example.com', 'customer2pw')
# index
get '/api/v1/organizations', {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(result.class, Array)
assert_equal(result.length, 1)
# show/:id
get "/api/v1/organizations/#{@organization.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal( result.class, Hash)
assert_equal( result['name'], 'Rest Org')
get "/api/v1/organizations/#{@organization2.id}", {}, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal( result.class, Hash)
assert_equal( result['name'], nil)
end
end

View file

@ -235,4 +235,13 @@ class ModelTest < ActiveSupport::TestCase
end
test 'searchable test' do
searchable = Models.searchable
assert(searchable.include?(Ticket))
assert(searchable.include?(User))
assert(searchable.include?(Organization))
assert_equal(3, searchable.count)
end
end

View file

@ -1,244 +0,0 @@
# encoding: utf-8
require 'test_helper'
class RestTest < ActiveSupport::TestCase
test 'users and orgs' do
if !ENV['BROWSER_URL']
puts 'NOTICE: Do not execute rest tests, no BROWSER_URL=http://some_host:port is defined! e. g. export BROWSER_URL=http://localhost:3000'
return
end
# create agent
roles = Role.where( name: %w(Admin Agent) )
groups = Group.all
UserInfo.current_user_id = 1
admin = User.create_or_update(
login: 'rest-admin',
firstname: 'Rest',
lastname: 'Agent',
email: 'rest-admin@example.com',
password: 'adminpw',
active: true,
roles: roles,
groups: groups,
)
# create agent
roles = Role.where( name: 'Agent' )
agent = User.create_or_update(
login: 'rest-agent@example.com',
firstname: 'Rest',
lastname: 'Agent',
email: 'rest-agent@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
# create customer without org
roles = Role.where( name: 'Customer' )
customer_without_org = User.create_or_update(
login: 'rest-customer1@example.com',
firstname: 'Rest',
lastname: 'Customer1',
email: 'rest-customer1@example.com',
password: 'customer1pw',
active: true,
roles: roles,
)
# create orgs
organization = Organization.create_or_update(
name: 'Rest Org',
)
organization2 = Organization.create_or_update(
name: 'Rest Org #2',
)
organization3 = Organization.create_or_update(
name: 'Rest Org #3',
)
# create customer with org
customer_with_org = User.create_or_update(
login: 'rest-customer2@example.com',
firstname: 'Rest',
lastname: 'Customer2',
email: 'rest-customer2@example.com',
password: 'customer2pw',
active: true,
roles: roles,
organization_id: organization.id,
)
# not existing user
request = get( 'not_existing@example.com', 'adminpw', '/api/v1/users')
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
# username auth, wrong pw
request = get( 'rest-admin', 'not_existing', '/api/v1/users' )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
# email auth, wrong pw
request = get( 'rest-admin@example.com', 'not_existing', '/api/v1/users' )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
# username auth
request = get( 'rest-admin', 'adminpw', '/api/v1/users' )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
# email auth
request = get( 'rest-admin@example.com', 'adminpw', '/api/v1/users' )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
# /users
# index
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/users')
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
assert( request[:data].length >= 3 )
# show/:id
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/users/' + agent.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['email'], 'rest-agent@example.com')
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/users/' + customer_without_org.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['email'], 'rest-customer1@example.com')
# index
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/users')
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
assert_equal( request[:data].length, 1 )
# show/:id
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/users/' + customer_without_org.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['email'], 'rest-customer1@example.com')
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/users/' + customer_with_org.id.to_s )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
# index
request = get( 'rest-customer2@example.com', 'customer2pw', '/api/v1/users')
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
assert_equal( request[:data].length, 1 )
# show/:id
request = get( 'rest-customer2@example.com', 'customer2pw', '/api/v1/users/' + customer_with_org.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['email'], 'rest-customer2@example.com')
request = get( 'rest-customer2@example.com', 'customer2pw', '/api/v1/users/' + customer_without_org.id.to_s )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
# /organizations
# index
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/organizations')
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
assert( request[:data].length >= 3 )
# show/:id
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/organizations/' + organization.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['name'], 'Rest Org')
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/organizations/' + organization2.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['name'], 'Rest Org #2')
# index
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/organizations')
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
assert_equal( request[:data].length, 0 )
# show/:id
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/organizations/' + organization.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['name'], nil)
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/organizations/' + organization2.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['name'], nil)
# index
request = get( 'rest-customer2@example.com', 'customer2pw', '/api/v1/organizations')
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
assert_equal( request[:data].length, 1 )
# show/:id
request = get( 'rest-customer2@example.com', 'customer2pw', '/api/v1/organizations/' + organization.id.to_s )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert_equal( request[:data]['name'], 'Rest Org')
request = get( 'rest-customer2@example.com', 'customer2pw', '/api/v1/organizations/' + organization2.id.to_s )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
# packages
request = get( 'rest-admin@example.com', 'adminpw', '/api/v1/packages' )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Hash)
assert( request[:data]['packages'] )
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/packages' )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/packages' )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
# settings
request = get( 'rest-admin@example.com', 'adminpw', '/api/v1/settings' )
assert_equal( request[:response].code, '200' )
assert_equal( request[:data].class, Array)
assert( request[:data][0] )
request = get( 'rest-agent@example.com', 'agentpw', '/api/v1/settings' )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
request = get( 'rest-customer1@example.com', 'customer1pw', '/api/v1/settings' )
assert_equal( request[:response].code, '401' )
assert_equal( request[:data].class, NilClass)
end
def get(user, pw, url)
response = UserAgent.get(
"#{ENV['BROWSER_URL']}#{url}",
{},
{
json: true,
user: user,
password: pw,
}
)
#puts 'URL: ' + url
#puts response.code.to_s
#puts response.body.to_s
{ data: response.data, response: response }
end
end

View file

@ -0,0 +1,190 @@
require 'test_helper'
class UserDeviceTest < ActiveSupport::TestCase
setup do
# create agent
groups = Group.all
roles = Role.where( name: 'Agent' )
UserInfo.current_user_id = 1
@agent = User.create_or_update(
login: 'user-device-agent@example.com',
firstname: 'UserDevice',
lastname: 'Agent',
email: 'user-device-agent@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
end
test 'session test' do
# signin with fingerprint A from country A via session -> new device #1
user_device1 = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'91.115.248.231',
@agent.id,
'fingerprint1234',
'session',
)
# signin with fingerprint A from country B via session -> new device #2
user_device2 = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'176.198.137.254',
@agent.id,
'fingerprint1234',
'session',
)
assert_not_equal(user_device1.id, user_device2.id)
# signin with fingerprint B from country A via session -> new device #3
user_device3 = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'91.115.248.231',
@agent.id,
'fingerprintABC',
'session',
)
assert_not_equal(user_device2.id, user_device3.id)
# signin with fingerprint A from country A via session -> new device #1
user_device4 = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'91.115.248.231',
@agent.id,
'fingerprint1234',
'session',
)
assert_equal(user_device1.id, user_device4.id)
# signin with fingerprint A from country B via session -> new device #2
user_device5 = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'176.198.137.254',
@agent.id,
'fingerprint1234',
'session',
)
assert_equal(user_device2.id, user_device5.id)
# signin with fingerprint B from country A via session -> new device #3
user_device6 = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'91.115.248.231',
@agent.id,
'fingerprintABC',
'session',
)
assert_equal(user_device3.id, user_device6.id)
end
test 'session test - user agent (unknown)' do
# known user agent
user_device1 = UserDevice.add(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
'91.115.248.231',
@agent.id,
nil,
'session',
)
assert_equal('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36', user_device1.user_agent)
assert_equal('Mac, Chrome', user_device1.name)
# unknown user agent
user_device2 = UserDevice.add(
'ABC 123',
'91.115.248.231',
@agent.id,
nil,
'session',
)
assert_equal('ABC 123', user_device2.user_agent)
assert_equal('ABC 123', user_device2.browser)
assert_equal('ABC 123', user_device2.name)
# partently known
user_device3 = UserDevice.add(
'Mozilla/5.0 (iPhone; CPU iPhone OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H143 Safari/600.1.4',
'91.115.248.231',
@agent.id,
nil,
'session',
)
assert_equal('Mozilla/5.0 (iPhone; CPU iPhone OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H143 Safari/600.1.4', user_device3.user_agent)
assert_equal('iPhone', user_device3.browser)
assert_equal('iPhone', user_device3.name)
end
test 'api test' do
# signin with ua from country A via basic auth -> new device #1
user_device1 = UserDevice.add(
'curl/7.43.0',
'91.115.248.231',
@agent.id,
nil,
'basic_auth',
)
# signin with ua from country B via basic auth -> new device #2
user_device2 = UserDevice.add(
'curl/7.43.0',
'176.198.137.254',
@agent.id,
nil,
'basic_auth',
)
assert_not_equal(user_device1.id, user_device2.id)
# signin with ua from country A via basic auth -> new device #1
user_device3 = UserDevice.add(
'curl/7.43.0',
'91.115.248.231',
@agent.id,
nil,
'basic_auth',
)
assert_equal(user_device1.id, user_device3.id)
# signin with ua from country B via basic auth -> new device #2
user_device4 = UserDevice.add(
'curl/7.43.0',
'176.198.137.254',
@agent.id,
nil,
'basic_auth',
)
assert_equal(user_device2.id, user_device4.id)
# signin with ua from country A via token auth -> new device #1
user_device5 = UserDevice.add(
'curl/7.43.0',
'91.115.248.231',
@agent.id,
nil,
'token_auth',
)
assert_equal(user_device1.id, user_device5.id)
# signin with ua from country B via token auth -> new device #2
user_device6 = UserDevice.add(
'curl/7.43.0',
'176.198.137.254',
@agent.id,
nil,
'token_auth',
)
assert_equal(user_device2.id, user_device6.id)
end
end