Organize directories
This commit is contained in:
parent
8a91c9b89b
commit
4898382f78
433 changed files with 0 additions and 0 deletions
|
|
@ -0,0 +1,256 @@
|
|||
class Index extends App.ControllerIntegrationBase
|
||||
featureIntegration: 'pgp_integration'
|
||||
featureName: 'PGP'
|
||||
featureConfig: 'pgp_config'
|
||||
description: [
|
||||
['PGP (Pretty Good Privacy) is a widely accepted method (or more precisely, a protocol) for sending digitally signed and encrypted messages.']
|
||||
]
|
||||
events:
|
||||
'change .js-switch input': 'switch'
|
||||
|
||||
render: =>
|
||||
super
|
||||
new Form(
|
||||
el: @$('.js-form')
|
||||
)
|
||||
|
||||
new App.HttpLog(
|
||||
el: @$('.js-log')
|
||||
facility: 'PGP'
|
||||
)
|
||||
|
||||
class Form extends App.Controller
|
||||
events:
|
||||
'click .js-addPublicKey': 'addPublicKey'
|
||||
'click .js-addPrivateKey': 'addPrivateKey'
|
||||
'click .js-updateGroup': 'updateGroup'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@render()
|
||||
|
||||
currentConfig: ->
|
||||
App.Setting.get('pgp_config')
|
||||
|
||||
setConfig: (value) ->
|
||||
App.Setting.set('pgp_config', value, {notify: true})
|
||||
|
||||
render: =>
|
||||
@config = @currentConfig()
|
||||
|
||||
@html App.view('integration/pgp')(
|
||||
config: @config
|
||||
)
|
||||
@keyList()
|
||||
@groupList()
|
||||
|
||||
keyList: =>
|
||||
new List(el: @$('.js-keyList'))
|
||||
|
||||
groupList: =>
|
||||
new Group(
|
||||
el: @$('.js-groupList')
|
||||
config: @config
|
||||
)
|
||||
|
||||
addPublicKey: =>
|
||||
new PublicKey(
|
||||
callback: @keyList
|
||||
)
|
||||
|
||||
addPrivateKey: =>
|
||||
new PrivateKey(
|
||||
callback: @keyList
|
||||
)
|
||||
|
||||
updateGroup: (e) =>
|
||||
params = App.ControllerForm.params(e)
|
||||
@setConfig(params)
|
||||
|
||||
class PublicKey extends App.ControllerModal
|
||||
buttonClose: true
|
||||
buttonCancel: true
|
||||
buttonSubmit: 'Add'
|
||||
autoFocusOnFirstInput: false
|
||||
head: 'Add Public Key'
|
||||
large: true
|
||||
|
||||
content: ->
|
||||
|
||||
# show start dialog
|
||||
content = $(App.view('integration/pgp_public_key_add')(
|
||||
head: 'Add Public Key'
|
||||
))
|
||||
content
|
||||
|
||||
onSubmit: (e) =>
|
||||
params = new FormData($(e.currentTarget).closest('form').get(0))
|
||||
params.set('try', true)
|
||||
if _.isEmpty(params.get('data'))
|
||||
params.delete('data')
|
||||
@formDisable(e)
|
||||
|
||||
@ajax(
|
||||
id: 'pgp-public_key-add'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/pgp/public_key"
|
||||
processData: false
|
||||
contentType: false
|
||||
cache: false
|
||||
data: params
|
||||
success: (data, status, xhr) =>
|
||||
@close()
|
||||
@callback()
|
||||
error: (data) =>
|
||||
@close()
|
||||
details = data.responseJSON || {}
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(details.error_human || details.error || 'The import failed.')
|
||||
timeout: 6000
|
||||
)
|
||||
|
||||
class PrivateKey extends App.ControllerModal
|
||||
buttonClose: true
|
||||
buttonCancel: true
|
||||
buttonSubmit: 'Add'
|
||||
autoFocusOnFirstInput: false
|
||||
head: 'Add Private Key'
|
||||
large: true
|
||||
|
||||
content: ->
|
||||
|
||||
# show start dialog
|
||||
content = $(App.view('integration/pgp_private_key_add')(
|
||||
head: 'Add Private Key'
|
||||
))
|
||||
content
|
||||
|
||||
onSubmit: (e) =>
|
||||
params = new FormData($(e.currentTarget).closest('form').get(0))
|
||||
params.set('try', true)
|
||||
if _.isEmpty(params.get('data'))
|
||||
params.delete('data')
|
||||
@formDisable(e)
|
||||
|
||||
@ajax(
|
||||
id: 'pgp-private_key-add'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/pgp/private_key"
|
||||
processData: false
|
||||
contentType: false
|
||||
cache: false
|
||||
data: params
|
||||
success: (data, status, xhr) =>
|
||||
@close()
|
||||
@callback()
|
||||
error: (data) =>
|
||||
@close()
|
||||
details = data.responseJSON || {}
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(details.error_human || details.error || 'The import failed.')
|
||||
timeout: 6000
|
||||
)
|
||||
|
||||
|
||||
class List extends App.Controller
|
||||
events:
|
||||
'click .js-remove': 'remove'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@load()
|
||||
|
||||
load: =>
|
||||
@ajax(
|
||||
id: 'pgp-list'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/integration/pgp/public_key"
|
||||
success: (data, status, xhr) =>
|
||||
@render(data)
|
||||
|
||||
error: (data, status) =>
|
||||
|
||||
# do not close window if request is aborted
|
||||
return if status is 'abort'
|
||||
|
||||
details = data.responseJSON || {}
|
||||
@notify(
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(details.error_human || details.error || 'Loading failed.')
|
||||
)
|
||||
|
||||
# do something
|
||||
)
|
||||
|
||||
render: (data) =>
|
||||
@html App.view('integration/pgp_list')(
|
||||
keyPairs: data
|
||||
)
|
||||
|
||||
remove: (e) =>
|
||||
e.preventDefault()
|
||||
id = $(e.currentTarget).parents('tr').data('id')
|
||||
return if !id
|
||||
|
||||
@ajax(
|
||||
id: 'pgp-list'
|
||||
type: 'DELETE'
|
||||
url: "#{@apiPath}/integration/pgp/public_key"
|
||||
data: JSON.stringify(id: id)
|
||||
success: (data, status, xhr) =>
|
||||
@load()
|
||||
|
||||
error: (data, status) =>
|
||||
|
||||
# do not close window if request is aborted
|
||||
return if status is 'abort'
|
||||
|
||||
details = data.responseJSON || {}
|
||||
@notify(
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(details.error_human || details.error || 'Server operation failed.')
|
||||
)
|
||||
)
|
||||
|
||||
class Group extends App.Controller
|
||||
constructor: ->
|
||||
super
|
||||
@render()
|
||||
|
||||
render: (data) =>
|
||||
groups = App.Group.search(sortBy: 'name', filter: active: true)
|
||||
@html App.view('integration/pgp_group')(
|
||||
groups: groups
|
||||
)
|
||||
for group in groups
|
||||
for type, selector of { default_sign: 'js-signDefault', default_encryption: 'js-encryptionDefault' }
|
||||
selected = true
|
||||
if @config?.group_id && @config.group_id[type]
|
||||
selected = @config.group_id[type][group.id.toString()]
|
||||
selection = App.UiElement.boolean.render(
|
||||
name: "group_id::#{type}::#{group.id}"
|
||||
multiple: false
|
||||
null: false
|
||||
nulloption: false
|
||||
value: selected
|
||||
class: 'form-control--small'
|
||||
)
|
||||
@$("[data-id=#{group.id}] .#{selector}").html(selection)
|
||||
|
||||
class State
|
||||
@current: ->
|
||||
App.Setting.get('pgp_integration')
|
||||
|
||||
App.Config.set(
|
||||
'Integrationpgp'
|
||||
{
|
||||
name: 'PGP'
|
||||
target: '#system/integration/pgp'
|
||||
description: 'PGP enables you to send digitally signed and encrypted messages.'
|
||||
controller: Index
|
||||
state: State
|
||||
}
|
||||
'NavBarIntegrations'
|
||||
)
|
||||
|
|
@ -0,0 +1,614 @@
|
|||
# coffeelint: disable=camel_case_classes
|
||||
class App.UiElement.ticket_perform_action
|
||||
@defaults: (attribute) ->
|
||||
defaults = ['ticket.state_id']
|
||||
|
||||
groups =
|
||||
ticket:
|
||||
name: 'Ticket'
|
||||
model: 'Ticket'
|
||||
article:
|
||||
name: 'Article'
|
||||
model: 'Article'
|
||||
|
||||
if attribute.notification
|
||||
groups.notification =
|
||||
name: 'Notification'
|
||||
model: 'Notification'
|
||||
|
||||
# merge config
|
||||
elements = {}
|
||||
for groupKey, groupMeta of groups
|
||||
if !groupMeta.model || !App[groupMeta.model]
|
||||
if groupKey is 'notification'
|
||||
elements["#{groupKey}.email"] = { name: 'email', display: 'Email' }
|
||||
elements["#{groupKey}.sms"] = { name: 'sms', display: 'SMS' }
|
||||
elements["#{groupKey}.webhook"] = { name: 'webhook', display: 'Webhook' }
|
||||
else if groupKey is 'article'
|
||||
elements["#{groupKey}.note"] = { name: 'note', display: 'Note' }
|
||||
else
|
||||
|
||||
for row in App[groupMeta.model].configure_attributes
|
||||
|
||||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids'
|
||||
|
||||
# ignore readonly attributes
|
||||
if !row.readonly
|
||||
config = _.clone(row)
|
||||
|
||||
switch config.tag
|
||||
when 'datetime'
|
||||
config.operator = ['static', 'relative']
|
||||
when 'tag'
|
||||
config.operator = ['add', 'remove']
|
||||
|
||||
elements["#{groupKey}.#{config.name}"] = config
|
||||
|
||||
# add ticket deletion action
|
||||
if attribute.ticket_delete
|
||||
elements['ticket.action'] =
|
||||
name: 'action'
|
||||
display: 'Action'
|
||||
tag: 'select'
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
delete: 'Delete'
|
||||
|
||||
[defaults, groups, elements]
|
||||
|
||||
@placeholder: (elementFull, attribute, params, groups, elements) ->
|
||||
item = $( App.view('generic/ticket_perform_action/row')( attribute: attribute ) )
|
||||
selector = @buildAttributeSelector(elementFull, groups, elements)
|
||||
item.find('.js-attributeSelector').prepend(selector)
|
||||
item
|
||||
|
||||
@render: (attribute, params = {}) ->
|
||||
|
||||
[defaults, groups, elements] = @defaults(attribute)
|
||||
|
||||
# return item
|
||||
item = $( App.view('generic/ticket_perform_action/index')( attribute: attribute ) )
|
||||
|
||||
# add filter
|
||||
item.on('click', '.js-rowActions .js-add', (e) =>
|
||||
element = $(e.target).closest('.js-filterElement')
|
||||
placeholder = @placeholder(item, attribute, params, groups, elements)
|
||||
if element.get(0)
|
||||
element.after(placeholder)
|
||||
else
|
||||
item.append(placeholder)
|
||||
placeholder.find('.js-attributeSelector select').trigger('change')
|
||||
@updateAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# remove filter
|
||||
item.on('click', '.js-rowActions .js-remove', (e) =>
|
||||
return if $(e.currentTarget).hasClass('is-disabled')
|
||||
$(e.target).closest('.js-filterElement').remove()
|
||||
@updateAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# change attribute selector
|
||||
item.on('change', '.js-attributeSelector select', (e) =>
|
||||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||
@rebuildAttributeSelectors(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||
@updateAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# change operator selector
|
||||
item.on('change', '.js-operator select', (e) =>
|
||||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||
)
|
||||
|
||||
# build initial params
|
||||
if _.isEmpty(params[attribute.name])
|
||||
|
||||
for groupAndAttribute in defaults
|
||||
|
||||
# build and append
|
||||
element = @placeholder(item, attribute, params, groups, elements)
|
||||
item.append(element)
|
||||
@rebuildAttributeSelectors(item, element, groupAndAttribute, elements, {}, attribute)
|
||||
|
||||
else
|
||||
|
||||
for groupAndAttribute, meta of params[attribute.name]
|
||||
|
||||
# build and append
|
||||
element = @placeholder(item, attribute, params, groups, elements)
|
||||
@rebuildAttributeSelectors(item, element, groupAndAttribute, elements, meta, attribute)
|
||||
item.append(element)
|
||||
|
||||
@disableRemoveForOneAttribute(item)
|
||||
item
|
||||
|
||||
@buildAttributeSelector: (elementFull, groups, elements) ->
|
||||
|
||||
# find first possible attribute
|
||||
selectedValue = ''
|
||||
elementFull.find('.js-attributeSelector select option').each(->
|
||||
if !selectedValue && !$(@).prop('disabled')
|
||||
selectedValue = $(@).val()
|
||||
)
|
||||
|
||||
selection = $('<select class="form-control"></select>')
|
||||
for groupKey, groupMeta of groups
|
||||
displayName = App.i18n.translateInline(groupMeta.name)
|
||||
selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKey}\"></optgroup>")
|
||||
optgroup = selection.find("optgroup.js-#{groupKey}")
|
||||
for elementKey, elementGroup of elements
|
||||
spacer = elementKey.split(/\./)
|
||||
if spacer[0] is groupKey
|
||||
attributeConfig = elements[elementKey]
|
||||
displayName = App.i18n.translateInline(attributeConfig.display)
|
||||
|
||||
selected = ''
|
||||
if elementKey is selectedValue
|
||||
selected = 'selected="selected"'
|
||||
optgroup.append("<option value=\"#{elementKey}\" #{selected}>#{displayName}</option>")
|
||||
selection
|
||||
|
||||
# disable - if we only have one attribute
|
||||
@disableRemoveForOneAttribute: (elementFull) ->
|
||||
if elementFull.find('.js-attributeSelector select').length > 1
|
||||
elementFull.find('.js-remove').removeClass('is-disabled')
|
||||
else
|
||||
elementFull.find('.js-remove').addClass('is-disabled')
|
||||
|
||||
@updateAttributeSelectors: (elementFull) ->
|
||||
|
||||
# enable all
|
||||
elementFull.find('.js-attributeSelector select option').prop('disabled', false)
|
||||
|
||||
# disable all used attributes
|
||||
elementFull.find('.js-attributeSelector select').each(->
|
||||
keyLocal = $(@).val()
|
||||
elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
|
||||
)
|
||||
|
||||
# disable - if we only have one attribute
|
||||
@disableRemoveForOneAttribute(elementFull)
|
||||
|
||||
@rebuildAttributeSelectors: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
|
||||
# set attribute
|
||||
if groupAndAttribute
|
||||
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
|
||||
|
||||
notificationTypeMatch = groupAndAttribute.match(/^notification.([\w]+)$/)
|
||||
articleTypeMatch = groupAndAttribute.match(/^article.([\w]+)$/)
|
||||
|
||||
if _.isArray(notificationTypeMatch) && notificationType = notificationTypeMatch[1]
|
||||
elementRow.find('.js-setAttribute').html('').addClass('hide')
|
||||
elementRow.find('.js-setArticle').html('').addClass('hide')
|
||||
@buildNotificationArea(notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
else if _.isArray(articleTypeMatch) && articleType = articleTypeMatch[1]
|
||||
elementRow.find('.js-setAttribute').html('').addClass('hide')
|
||||
elementRow.find('.js-setNotification').html('').addClass('hide')
|
||||
@buildArticleArea(articleType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
else
|
||||
elementRow.find('.js-setNotification').html('').addClass('hide')
|
||||
elementRow.find('.js-setArticle').html('').addClass('hide')
|
||||
if !elementRow.find('.js-setAttribute div').get(0)
|
||||
attributeSelectorElement = $( App.view('generic/ticket_perform_action/attribute_selector')(
|
||||
attribute: attribute
|
||||
name: name
|
||||
meta: meta || {}
|
||||
))
|
||||
elementRow.find('.js-setAttribute').html(attributeSelectorElement).removeClass('hide')
|
||||
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
|
||||
if !meta.operator
|
||||
meta.operator = currentOperator
|
||||
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::operator"
|
||||
|
||||
selection = $("<select class=\"form-control\" name=\"#{name}\"></select>")
|
||||
attributeConfig = elements[groupAndAttribute]
|
||||
if !attributeConfig || !attributeConfig.operator
|
||||
elementRow.find('.js-operator').parent().addClass('hide')
|
||||
else
|
||||
elementRow.find('.js-operator').parent().removeClass('hide')
|
||||
if attributeConfig && attributeConfig.operator
|
||||
for operator in attributeConfig.operator
|
||||
operatorName = App.i18n.translateInline(operator)
|
||||
selected = ''
|
||||
if meta.operator is operator
|
||||
selected = 'selected="selected"'
|
||||
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
|
||||
selection
|
||||
|
||||
elementRow.find('.js-operator select').replaceWith(selection)
|
||||
|
||||
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||
|
||||
if !meta.pre_condition
|
||||
meta.pre_condition = currentPreCondition
|
||||
|
||||
toggleValue = =>
|
||||
preCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||
if preCondition isnt 'specific'
|
||||
elementRow.find('.js-value select').html('')
|
||||
elementRow.find('.js-value').addClass('hide')
|
||||
else
|
||||
elementRow.find('.js-value').removeClass('hide')
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
# force to use auto complition on user lookup
|
||||
attribute = _.clone(attributeConfig)
|
||||
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::value"
|
||||
attributeSelected = elements[groupAndAttribute]
|
||||
|
||||
preCondition = false
|
||||
if attributeSelected.relation is 'User'
|
||||
preCondition = 'user'
|
||||
attribute.tag = 'user_autocompletion'
|
||||
if attributeSelected.relation is 'Organization'
|
||||
preCondition = 'org'
|
||||
attribute.tag = 'autocompletion_ajax'
|
||||
if !preCondition
|
||||
elementRow.find('.js-preCondition select').html('')
|
||||
elementRow.find('.js-preCondition').closest('.controls').addClass('hide')
|
||||
toggleValue()
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
return
|
||||
|
||||
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::pre_condition"
|
||||
|
||||
selection = $("<select class=\"form-control\" name=\"#{name}\" ></select>")
|
||||
options = {}
|
||||
if preCondition is 'user'
|
||||
options =
|
||||
'current_user.id': App.i18n.translateInline('current user')
|
||||
'specific': App.i18n.translateInline('specific user')
|
||||
|
||||
if attributeSelected.null is true
|
||||
options['not_set'] = App.i18n.translateInline('unassign user')
|
||||
|
||||
else if preCondition is 'org'
|
||||
options =
|
||||
'current_user.organization_id': App.i18n.translateInline('current user organization')
|
||||
'specific': App.i18n.translateInline('specific organization')
|
||||
|
||||
for key, value of options
|
||||
selected = ''
|
||||
if key is meta.pre_condition
|
||||
selected = 'selected="selected"'
|
||||
selection.append("<option value=\"#{key}\" #{selected}>#{App.i18n.translateInline(value)}</option>")
|
||||
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
|
||||
elementRow.find('.js-preCondition select').replaceWith(selection)
|
||||
|
||||
elementRow.find('.js-preCondition select').on('change', (e) ->
|
||||
toggleValue()
|
||||
)
|
||||
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
toggleValue()
|
||||
|
||||
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::value"
|
||||
|
||||
# build new item
|
||||
attributeConfig = elements[groupAndAttribute]
|
||||
config = _.clone(attributeConfig)
|
||||
|
||||
if config.relation is 'User'
|
||||
config.tag = 'user_autocompletion'
|
||||
if config.relation is 'Organization'
|
||||
config.tag = 'autocompletion_ajax'
|
||||
|
||||
# render ui element
|
||||
item = ''
|
||||
if config && App.UiElement[config.tag]
|
||||
config['name'] = name
|
||||
if attribute.value && attribute.value[groupAndAttribute]
|
||||
config['value'] = _.clone(attribute.value[groupAndAttribute]['value'])
|
||||
config.multiple = false
|
||||
config.nulloption = config.null
|
||||
if config.tag is 'checkbox'
|
||||
config.tag = 'select'
|
||||
tagSearch = "#{config.tag}_search"
|
||||
if config.tag is 'datetime'
|
||||
config.validationContainer = 'self'
|
||||
if App.UiElement[tagSearch]
|
||||
item = App.UiElement[tagSearch].render(config, {})
|
||||
else
|
||||
item = App.UiElement[config.tag].render(config, {})
|
||||
|
||||
relative_operators = [
|
||||
'before (relative)',
|
||||
'within next (relative)',
|
||||
'within last (relative)',
|
||||
'after (relative)',
|
||||
'till (relative)',
|
||||
'from (relative)',
|
||||
'relative'
|
||||
]
|
||||
|
||||
upcoming_operator = meta.operator
|
||||
|
||||
if !_.include(config.operator, upcoming_operator)
|
||||
if Array.isArray(config.operator)
|
||||
upcoming_operator = config.operator[0]
|
||||
else
|
||||
upcoming_operator = null
|
||||
|
||||
if _.include(relative_operators, upcoming_operator)
|
||||
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
|
||||
if attribute.value && attribute.value[groupAndAttribute]
|
||||
config['value'] = _.clone(attribute.value[groupAndAttribute])
|
||||
item = App.UiElement['time_range'].render(config, {})
|
||||
|
||||
elementRow.find('.js-setAttribute > .flex > .js-value').removeClass('hide').html(item)
|
||||
|
||||
@buildNotificationArea: (notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
|
||||
return if elementRow.find(".js-setNotification .js-body-#{notificationType}").get(0)
|
||||
|
||||
elementRow.find('.js-setNotification').empty()
|
||||
|
||||
options =
|
||||
'article_last_sender': 'Sender of last article'
|
||||
'ticket_owner': 'Owner'
|
||||
'ticket_customer': 'Customer'
|
||||
'ticket_agents': 'All agents'
|
||||
|
||||
name = "#{attribute.name}::notification.#{notificationType}"
|
||||
|
||||
messageLength = switch notificationType
|
||||
when 'sms' then 160
|
||||
else 200000
|
||||
|
||||
# meta.recipient was a string in the past (single-select) so we convert it to array if needed
|
||||
if !_.isArray(meta.recipient)
|
||||
meta.recipient = [meta.recipient]
|
||||
|
||||
columnSelectOptions = []
|
||||
for key, value of options
|
||||
selected = undefined
|
||||
for recipient in meta.recipient
|
||||
if key is recipient
|
||||
selected = true
|
||||
columnSelectOptions.push({ value: key, name: App.i18n.translatePlain(value), selected: selected })
|
||||
|
||||
columnSelectRecipientUserOptions = []
|
||||
for user in App.User.all()
|
||||
key = "userid_#{user.id}"
|
||||
selected = undefined
|
||||
for recipient in meta.recipient
|
||||
if key is recipient
|
||||
selected = true
|
||||
columnSelectRecipientUserOptions.push({ value: key, name: "#{user.firstname} #{user.lastname}", selected: selected })
|
||||
|
||||
columnSelectRecipient = new App.ColumnSelect
|
||||
attribute:
|
||||
name: "#{name}::recipient"
|
||||
options: [
|
||||
{
|
||||
label: 'Variables',
|
||||
group: columnSelectOptions
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
group: columnSelectRecipientUserOptions
|
||||
},
|
||||
]
|
||||
|
||||
selectionRecipient = columnSelectRecipient.element()
|
||||
|
||||
if notificationType is 'webhook'
|
||||
notificationElement = $( App.view('generic/ticket_perform_action/webhook')(
|
||||
attribute: attribute
|
||||
name: name
|
||||
notificationType: notificationType
|
||||
meta: meta || {}
|
||||
))
|
||||
|
||||
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
|
||||
|
||||
|
||||
if App.Webhook.search(filter: { active: true }).length isnt 0 || !_.isEmpty(meta.webhook_id)
|
||||
webhookSelection = App.UiElement.select.render(
|
||||
name: "#{name}::webhook_id"
|
||||
multiple: false
|
||||
null: false
|
||||
relation: 'Webhook'
|
||||
value: meta.webhook_id
|
||||
translate: false
|
||||
nulloption: true
|
||||
)
|
||||
else
|
||||
webhookSelection = App.view('generic/ticket_perform_action/webhook_not_available')( attribute: attribute )
|
||||
|
||||
notificationElement.find('.js-webhooks').html(webhookSelection)
|
||||
|
||||
else
|
||||
notificationElement = $( App.view('generic/ticket_perform_action/notification')(
|
||||
attribute: attribute
|
||||
name: name
|
||||
notificationType: notificationType
|
||||
meta: meta || {}
|
||||
))
|
||||
|
||||
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
|
||||
|
||||
visibilitySelection = App.UiElement.select.render(
|
||||
name: "#{name}::internal"
|
||||
multiple: false
|
||||
null: false
|
||||
options: { true: 'internal', false: 'public' }
|
||||
value: meta.internal || 'false'
|
||||
translate: true
|
||||
)
|
||||
|
||||
includeAttachmentsCheckbox = App.UiElement.select.render(
|
||||
name: "#{name}::include_attachments"
|
||||
multiple: false
|
||||
null: false
|
||||
options: { true: 'Yes', false: 'No' }
|
||||
value: meta.include_attachments || 'false'
|
||||
translate: true
|
||||
)
|
||||
|
||||
notificationElement.find('.js-internal').html(visibilitySelection)
|
||||
notificationElement.find('.js-include_attachments').html(includeAttachmentsCheckbox)
|
||||
|
||||
notificationElement.find('.js-body div[contenteditable="true"]').ce(
|
||||
mode: 'richtext'
|
||||
placeholder: 'message'
|
||||
maxlength: messageLength
|
||||
)
|
||||
new App.WidgetPlaceholder(
|
||||
el: notificationElement.find('.js-body div[contenteditable="true"]').parent()
|
||||
objects: [
|
||||
{
|
||||
prefix: 'ticket'
|
||||
object: 'Ticket'
|
||||
display: 'Ticket'
|
||||
},
|
||||
{
|
||||
prefix: 'article'
|
||||
object: 'TicketArticle'
|
||||
display: 'Article'
|
||||
},
|
||||
{
|
||||
prefix: 'user'
|
||||
object: 'User'
|
||||
display: 'Current User'
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
elementRow.find('.js-setNotification').html(notificationElement).removeClass('hide')
|
||||
|
||||
if App.Config.get('smime_integration') == true || App.Config.get('pgp_integration') == true
|
||||
selection = App.UiElement.select.render(
|
||||
name: "#{name}::sign"
|
||||
multiple: false
|
||||
options: {
|
||||
'no': 'Do not sign email'
|
||||
'discard': 'Sign email (if not possible, discard notification)'
|
||||
'always': 'Sign email (if not possible, send notification anyway)'
|
||||
}
|
||||
value: meta.sign
|
||||
translate: true
|
||||
)
|
||||
|
||||
elementRow.find('.js-sign').html(selection)
|
||||
|
||||
selection = App.UiElement.select.render(
|
||||
name: "#{name}::encryption"
|
||||
multiple: false
|
||||
options: {
|
||||
'no': 'Do not encrypt email'
|
||||
'discard': 'Encrypt email (if not possible, discard notification)'
|
||||
'always': 'Encrypt email (if not possible, send notification anyway)'
|
||||
}
|
||||
value: meta.encryption
|
||||
translate: true
|
||||
)
|
||||
|
||||
elementRow.find('.js-encryption').html(selection)
|
||||
|
||||
@buildArticleArea: (articleType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
|
||||
return if elementRow.find(".js-setArticle .js-body-#{articleType}").get(0)
|
||||
|
||||
elementRow.find('.js-setArticle').empty()
|
||||
|
||||
name = "#{attribute.name}::article.#{articleType}"
|
||||
selection = App.UiElement.select.render(
|
||||
name: "#{name}::internal"
|
||||
multiple: false
|
||||
null: false
|
||||
label: 'Visibility'
|
||||
options: { true: 'internal', false: 'public' }
|
||||
value: meta.internal
|
||||
translate: true
|
||||
)
|
||||
articleElement = $( App.view('generic/ticket_perform_action/article')(
|
||||
attribute: attribute
|
||||
name: name
|
||||
articleType: articleType
|
||||
meta: meta || {}
|
||||
))
|
||||
articleElement.find('.js-internal').html(selection)
|
||||
articleElement.find('.js-body div[contenteditable="true"]').ce(
|
||||
mode: 'richtext'
|
||||
placeholder: 'message'
|
||||
maxlength: 200000
|
||||
)
|
||||
new App.WidgetPlaceholder(
|
||||
el: articleElement.find('.js-body div[contenteditable="true"]').parent()
|
||||
objects: [
|
||||
{
|
||||
prefix: 'ticket'
|
||||
object: 'Ticket'
|
||||
display: 'Ticket'
|
||||
},
|
||||
{
|
||||
prefix: 'article'
|
||||
object: 'TicketArticle'
|
||||
display: 'Article'
|
||||
},
|
||||
{
|
||||
prefix: 'user'
|
||||
object: 'User'
|
||||
display: 'Current User'
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
elementRow.find('.js-setArticle').html(articleElement).removeClass('hide')
|
||||
|
||||
@humanText: (condition) ->
|
||||
none = App.i18n.translateContent('No filter.')
|
||||
return [none] if _.isEmpty(condition)
|
||||
[defaults, groups, operators, elements] = @defaults()
|
||||
rules = []
|
||||
for attribute, value of condition
|
||||
|
||||
objectAttribute = attribute.split(/\./)
|
||||
|
||||
# get stored params
|
||||
if meta && objectAttribute[1]
|
||||
model = toCamelCase(objectAttribute[0])
|
||||
config = elements[attribute]
|
||||
|
||||
valueHuman = []
|
||||
if _.isArray(value)
|
||||
for data in value
|
||||
r = @humanTextLookup(config, data)
|
||||
valueHuman.push r
|
||||
else
|
||||
valueHuman.push @humanTextLookup(config, value)
|
||||
|
||||
if valueHuman.join
|
||||
valueHuman = valueHuman.join(', ')
|
||||
rules.push "#{App.i18n.translateContent('Set')} <b>#{App.i18n.translateContent(model)} -> #{App.i18n.translateContent(config.display)}</b> #{App.i18n.translateContent('to')} <b>#{valueHuman}</b>."
|
||||
|
||||
return [none] if _.isEmpty(rules)
|
||||
rules
|
||||
|
||||
@humanTextLookup: (config, value) ->
|
||||
return value if !App[config.relation]
|
||||
return value if !App[config.relation].exists(value)
|
||||
data = App[config.relation].fullLocal(value)
|
||||
return value if !data
|
||||
if data.displayName
|
||||
return App.i18n.translateContent( data.displayName() )
|
||||
valueHuman.push App.i18n.translateContent( data.name )
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
# Methods for displaying security ui elements and to get security params
|
||||
|
||||
App.SecurityOptions =
|
||||
|
||||
securityOptionsShow: ->
|
||||
@$('.js-securityOptions').removeClass('hide')
|
||||
|
||||
securityOptionsHide: ->
|
||||
@$('.js-securityOptions').addClass('hide')
|
||||
|
||||
securityOptionsShown: ->
|
||||
!@$('.js-securityOptions').hasClass('hide')
|
||||
|
||||
securityEnabled: ->
|
||||
App.Config.get('smime_integration') || App.Config.get('pgp_integration')
|
||||
|
||||
paramsSecurity: =>
|
||||
if @$('.js-securityOptions').hasClass('hide')
|
||||
return {}
|
||||
|
||||
security = {}
|
||||
security.encryption ||= {}
|
||||
security.sign ||= {}
|
||||
if App.Config.get('pgp_integration')
|
||||
security.type = 'PGP'
|
||||
else
|
||||
security.type = 'S/MIME'
|
||||
if @$('.js-securityEncrypt').hasClass('btn--active')
|
||||
security.encryption.success = true
|
||||
if @$('.js-securitySign').hasClass('btn--active')
|
||||
security.sign.success = true
|
||||
security
|
||||
|
||||
updateSecurityOptionsRemote: (key, ticket, article, securityOptions) ->
|
||||
if securityOptions.type == 'PGP'
|
||||
id = "pgp-check-#{key}"
|
||||
url = "#{@apiPath}/integration/pgp"
|
||||
securityConfig = App.Config.get('pgp_config')
|
||||
else
|
||||
id = "smime-check-#{key}"
|
||||
url = "#{@apiPath}/integration/smime"
|
||||
securityConfig = App.Config.get('smime_config')
|
||||
callback = =>
|
||||
@ajax(
|
||||
id: id
|
||||
type: 'POST'
|
||||
url: url
|
||||
data: JSON.stringify(ticket: ticket, article: article)
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# get default selected security options
|
||||
selected =
|
||||
encryption: true
|
||||
sign: true
|
||||
for type, selector of { default_sign: 'sign', default_encryption: 'encryption' }
|
||||
if securityConfig?.group_id?[type] && ticket.group_id
|
||||
if securityConfig.group_id[type][ticket.group_id.toString()] == false
|
||||
selected[selector] = false
|
||||
|
||||
@$('.js-securityEncryptComment').attr('title', data.encryption.comment)
|
||||
|
||||
# if encryption is possible
|
||||
if data.encryption.success is true
|
||||
@$('.js-securityEncrypt').attr('disabled', false)
|
||||
|
||||
# overrule current selection with Group configuration
|
||||
if selected.encryption
|
||||
@$('.js-securityEncrypt').addClass('btn--active')
|
||||
else
|
||||
@$('.js-securityEncrypt').removeClass('btn--active')
|
||||
|
||||
# if encryption is not possible
|
||||
else
|
||||
@$('.js-securityEncrypt').attr('disabled', true)
|
||||
@$('.js-securityEncrypt').removeClass('btn--active')
|
||||
|
||||
@$('.js-securitySignComment').attr('title', data.sign.comment)
|
||||
|
||||
# if sign is possible
|
||||
if data.sign.success is true
|
||||
@$('.js-securitySign').attr('disabled', false)
|
||||
|
||||
# overrule current selection with Group configuration
|
||||
if selected.sign
|
||||
@$('.js-securitySign').addClass('btn--active')
|
||||
else
|
||||
@$('.js-securitySign').removeClass('btn--active')
|
||||
|
||||
# if sign is possible
|
||||
else
|
||||
@$('.js-securitySign').attr('disabled', true)
|
||||
@$('.js-securitySign').removeClass('btn--active')
|
||||
|
||||
error: (data) ->
|
||||
details = data.responseJSON || {}
|
||||
console.log(details)
|
||||
)
|
||||
@delay(callback, 200, 'security-check')
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<form>
|
||||
<h2><%- @T('Public & Private Keys') %></h2>
|
||||
<div class="settings-entry settings-entry--stretched js-keyList"></div>
|
||||
|
||||
<div class="btn btn--primary js-addPublicKey"><%- @T('Add Public Key') %></div>
|
||||
<div class="btn js-addPrivateKey"><%- @T('Add Private Key') %></div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2><%- @T('Default Behavior') %></h2>
|
||||
<p>Choose the default behavior of the PGP integration on per group basis. If signing or encrypting is not possible, the setting has no effect. Agents call always manually alter the behavior for each article.</p>
|
||||
<div class="settings-entry settings-entry--stretched js-groupList"></div>
|
||||
<div class="btn btn--primary js-updateGroup"><%- @T('Update') %></div>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<table class="settings-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="55%"><%- @T('Group') %>
|
||||
<th><%- @T('Sign') %>
|
||||
<th><%- @T('Encryption') %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if _.isEmpty(@groups): %>
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<%- @T('No Entries') %>
|
||||
</td>
|
||||
</tr>
|
||||
<% else: %>
|
||||
<% for group in @groups: %>
|
||||
<tr data-id="<%= group.id %>">
|
||||
<td><%= group.name %>
|
||||
<td class="js-signDefault">
|
||||
<td class="js-encryptionDefault">
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<table class="settings-list settings-list--stretch">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="35%"><%- @T('Email') %>
|
||||
<th width="60%"><%- @T('Fingerprint') %>
|
||||
<th width="5%"><%- @T('Actions') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if _.isEmpty(@keyPairs): %>
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<%- @T('No Entries') %>
|
||||
</td>
|
||||
</tr>
|
||||
<% else: %>
|
||||
<% for keyPair in @keyPairs: %>
|
||||
<tr data-id="<%= keyPair.id %>">
|
||||
<td><% if !_.isEmpty(keyPair.email_addresses): %><%= keyPair.email_addresses.toString() %><% end %>
|
||||
<% if keyPair.private_key: %><br><i><%- @T('Including private key.') %></i><% end %>
|
||||
<td title="<%= keyPair.fingerprint %>"><%= keyPair.fingerprint %>
|
||||
<td>
|
||||
<div class="dropdown dropdown--actions">
|
||||
<div class="btn btn--table btn--text btn--secondary js-action" data-toggle="dropdown">
|
||||
<%- @Icon('overflow-button') %>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-right js-table-action-menu" role="menu">
|
||||
<% if keyPair.private_key: %>
|
||||
<li role="presentation" data-table-action="download-private">
|
||||
<a href="<%= @C('http_type') %>://<%= @C('fqdn')%>/api/v1/integration/pgp/private_key_download/<%= keyPair.id %>" download><%- @Icon('download') %> <%- @T('Download Private Key') %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
<li role="presentation" data-table-action="download-public">
|
||||
<a href="<%= @C('http_type') %>://<%= @C('fqdn')%>/api/v1/integration/pgp/public_key_download/<%= keyPair.id %>"%download><%- @Icon('download') %> <%- @T('Download Public Key') %></a>
|
||||
</li>
|
||||
<li role="presentation" class="danger js-remove" data-table-action="remove">
|
||||
<%- @Icon('trash') %> <%- @T('Delete') %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<div>
|
||||
<p class="alert alert--danger js-error hide"></p>
|
||||
|
||||
<div class="form-field-group">
|
||||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for="private_key-upload"><%- @T('Upload Private Key') %></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input name="file" type="file" id="private_key-upload">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="or-divider">
|
||||
<span><%- @T('or') %></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for="private_key-paste"><%- @T('Paste Private Key') %></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<textarea cols="25" rows="20" name="data" style="height: 200px;"
|
||||
id="private_key-paste"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for="private_key-secret"><%- @T('Enter Private Key Secret') %></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input class="form-control" name="secret" type="password" id="private_key-secret">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<div>
|
||||
<p class="alert alert--danger js-error hide"></p>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for="public_key-upload"><%- @T('Upload Public Key') %></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input name="file" type="file" id="public_key-upload">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="or-divider">
|
||||
<span><%- @T('or') %></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for="public_key-paste"><%- @T('Paste Public Key') %></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<textarea cols="25" rows="20" name="data" style="height: 200px;"
|
||||
id="public_key-paste"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1 @@
|
|||
.icon-pgp { width:17px; height: 17px; }
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class Integration::PGPController < ApplicationController
|
||||
prepend_before_action { authentication_check && authorize! }
|
||||
|
||||
def public_key_download
|
||||
cert = PGPKeypair.find(params[:id])
|
||||
|
||||
send_data(
|
||||
cert.public_key,
|
||||
filename: "#{cert.fingerprint}.asc",
|
||||
type: 'text/plain',
|
||||
disposition: 'attachment'
|
||||
)
|
||||
end
|
||||
|
||||
def private_key_download
|
||||
cert = PGPKeypair.find(params[:id])
|
||||
|
||||
send_data(
|
||||
cert.private_key,
|
||||
filename: "#{cert.fingerprint}-private.asc",
|
||||
type: 'text/plain',
|
||||
disposition: 'attachment'
|
||||
)
|
||||
end
|
||||
|
||||
def public_key_list
|
||||
render json: PGPKeypair.all, methods: :email_addresses
|
||||
end
|
||||
|
||||
def public_key_delete
|
||||
PGPKeypair.find(params[:id]).destroy!
|
||||
render json: {
|
||||
result: 'ok'
|
||||
}
|
||||
end
|
||||
|
||||
def public_key_add
|
||||
string = params[:data]
|
||||
string = params[:file].read.force_encoding('utf-8') if string.blank? && params[:file].present?
|
||||
|
||||
items = PGPKeypair.create_public_keys(string)
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
response: items
|
||||
}
|
||||
rescue StandardError => e
|
||||
unprocessable_entity(e)
|
||||
end
|
||||
|
||||
def private_key_delete
|
||||
PGPKeypair.find(params[:id]).update!(
|
||||
private_key: nil,
|
||||
private_key_secret: nil
|
||||
)
|
||||
|
||||
render json: {
|
||||
result: 'ok'
|
||||
}
|
||||
end
|
||||
|
||||
def private_key_add
|
||||
string = params[:data]
|
||||
string = params[:file].read.force_encoding('utf-8') if string.blank? && params[:file].present?
|
||||
|
||||
raise "Parameter 'data' or 'file' required." if string.blank?
|
||||
|
||||
PGPKeypair.create_private_keys(string, params[:secret])
|
||||
|
||||
render json: {
|
||||
result: 'ok'
|
||||
}
|
||||
rescue StandardError => e
|
||||
unprocessable_entity(e)
|
||||
end
|
||||
|
||||
def search
|
||||
result = {
|
||||
type: 'PGP'
|
||||
}
|
||||
|
||||
result[:encryption] = article_encryption(params[:article])
|
||||
result[:sign] = article_sign(params[:ticket])
|
||||
|
||||
render json: result
|
||||
end
|
||||
|
||||
def article_encryption(article)
|
||||
result = {
|
||||
success: false,
|
||||
comment: 'no recipient found'
|
||||
}
|
||||
|
||||
return result if article.blank?
|
||||
return result if article[:to].blank? && article[:cc].blank?
|
||||
|
||||
recipient = [article[:to], article[:cc]].compact.join(',').to_s
|
||||
recipients = []
|
||||
begin
|
||||
list = Mail::AddressList.new(recipient)
|
||||
list.addresses.each do |address|
|
||||
recipients.push address.address
|
||||
end
|
||||
rescue StandardError # rubocop:disable Lint/SuppressedException
|
||||
end
|
||||
|
||||
return result if recipients.blank?
|
||||
|
||||
begin
|
||||
keys = PGPKeypair.for_recipient_email_addresses!(recipients)
|
||||
|
||||
if keys
|
||||
result[:success] = true
|
||||
result[:comment] = "keys found for #{recipients.join(',')}"
|
||||
end
|
||||
rescue StandardError => e
|
||||
result[:comment] = e.message
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def article_sign(ticket)
|
||||
result = {
|
||||
success: false,
|
||||
comment: 'key not found'
|
||||
}
|
||||
|
||||
return result if ticket.blank? || !ticket[:group_id]
|
||||
|
||||
group = Group.find_by(id: ticket[:group_id])
|
||||
return result unless group
|
||||
|
||||
email_address = group.email_address
|
||||
begin
|
||||
list = Mail::AddressList.new(email_address.email)
|
||||
from = list.addresses.first.to_s
|
||||
key = PGPKeypair.for_sender_email_address(from)
|
||||
if key
|
||||
result[:success] = true
|
||||
result[:comment] = "key for #{email_address.email} found"
|
||||
else
|
||||
result[:success] = false
|
||||
result[:comment] = "no key for #{email_address.email} found"
|
||||
end
|
||||
rescue StandardError => e
|
||||
result[:comment] = e.message
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
64
packages/zammad-addon-pgp/src/app/models/pgp_keypair.rb
Normal file
64
packages/zammad-addon-pgp/src/app/models/pgp_keypair.rb
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class PGPKeypair < ApplicationModel
|
||||
validates :fingerprint, uniqueness: { case_sensitive: true }
|
||||
|
||||
def self.create_private_keys(raw, secret)
|
||||
Sequoia.emails_of(keys: raw).each do |address|
|
||||
downcased_address = address.downcase
|
||||
public_key = find_each.detect do |certificate|
|
||||
certificate.email_addresses.include?(downcased_address)
|
||||
end
|
||||
|
||||
unless public_key
|
||||
raise Exceptions::UnprocessableEntity,
|
||||
'The public key for this private key could not be found.'
|
||||
end
|
||||
|
||||
public_key.update!(private_key: raw, private_key_secret: secret)
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_public_keys(raw)
|
||||
create!(public_key: raw)
|
||||
end
|
||||
|
||||
def self.for_sender_email_address(address)
|
||||
downcased_address = address.downcase
|
||||
where.not(private_key: nil).find_each.detect do |certificate|
|
||||
certificate.email_addresses.include?(downcased_address)
|
||||
end
|
||||
end
|
||||
|
||||
def self.for_recipient_email_addresses!(addresses)
|
||||
certificates = []
|
||||
remaining_addresses = addresses.map(&:downcase)
|
||||
find_each do |certificate|
|
||||
# intersection of both lists
|
||||
certificate_for = certificate.email_addresses & remaining_addresses
|
||||
next if certificate_for.blank?
|
||||
|
||||
certificates.push(certificate)
|
||||
|
||||
# subtract found recipient(s)
|
||||
remaining_addresses -= certificate_for
|
||||
|
||||
# end loop if no addresses are remaining
|
||||
break if remaining_addresses.blank?
|
||||
end
|
||||
|
||||
return certificates if remaining_addresses.blank?
|
||||
|
||||
raise ActiveRecord::RecordNotFound,
|
||||
"Can't find PGP encryption certificates for: #{remaining_addresses.join(', ')}"
|
||||
end
|
||||
|
||||
def public_key=(string)
|
||||
self.fingerprint = Sequoia.fingerprints_of(keys: string).first
|
||||
self[:public_key] = string
|
||||
end
|
||||
|
||||
def email_addresses
|
||||
@email_addresses ||= Sequoia.emails_of(keys: public_key)
|
||||
end
|
||||
end
|
||||
1909
packages/zammad-addon-pgp/src/app/models/ticket.rb
Normal file
1909
packages/zammad-addon-pgp/src/app/models/ticket.rb
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,6 @@
|
|||
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class Controllers::Integration::PGPControllerPolicy < Controllers::ApplicationControllerPolicy
|
||||
permit! :search, to: 'ticket.agent'
|
||||
default_permit!('admin.integration.pgp')
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue