Compare commits
180 Commits
stable-5.1
...
1.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd2ad7a88d | ||
|
|
6630a3f624 | ||
|
|
1004691fea | ||
|
|
0f6a3ed2a3 | ||
|
|
f78f2bf2a9 | ||
|
|
64a7267fc6 | ||
|
|
33c371b837 | ||
|
|
7e6335c3cd | ||
|
|
6062f6defc | ||
|
|
de04f977a9 | ||
|
|
fc58aa79ac | ||
|
|
c59cd7b992 | ||
|
|
c9c80d3e72 | ||
|
|
be2d6a45ca | ||
|
|
2e9a3a4d04 | ||
|
|
041b03b4f3 | ||
|
|
8dde0f2832 | ||
|
|
b1ef5e2d83 | ||
|
|
a01cbd60d6 | ||
|
|
17eb0f84ec | ||
|
|
56d0523c1f | ||
|
|
9fa18758bc | ||
|
|
d2f2c55b06 | ||
|
|
f4a409bd6e | ||
|
|
971b724d4c | ||
|
|
3263c6f2ba | ||
|
|
359b026535 | ||
|
|
c63518fd3e | ||
|
|
24ba3cff44 | ||
|
|
f7066e1995 | ||
|
|
6844499309 | ||
|
|
afa3e47eab | ||
|
|
5f3e9298f1 | ||
|
|
e4dd303357 | ||
|
|
2fa06eee3d | ||
|
|
dc0ac03334 | ||
|
|
c5a1d69ec5 | ||
|
|
9891981f92 | ||
|
|
2c721f71e7 | ||
|
|
0b4a4be0b7 | ||
|
|
b13bb2fcaf | ||
|
|
d9ba369fcf | ||
|
|
3adece0db5 | ||
|
|
7101ffc277 | ||
|
|
19a7a439b8 | ||
|
|
e728320241 | ||
|
|
1bc17afc53 | ||
|
|
ee5c58f53a | ||
|
|
9bdf6d7b81 | ||
|
|
5754fdccdd | ||
|
|
4901050d57 | ||
|
|
7980c8cb93 | ||
|
|
7e7088388d | ||
|
|
2f6e52d63d | ||
|
|
bde8babb43 | ||
|
|
d6d68fb363 | ||
|
|
814ffed0f0 | ||
|
|
b013fb8cbc | ||
|
|
fbd8e63592 | ||
|
|
e8ad3965d7 | ||
|
|
e00981ee23 | ||
|
|
4e74c6beda | ||
|
|
2d09e556d7 | ||
|
|
8cd39398d6 | ||
|
|
94c616ffe3 | ||
|
|
57649bb888 | ||
|
|
7e9442ec4a | ||
|
|
521e6265fd | ||
|
|
a42a9689fc | ||
|
|
d9ffb8001d | ||
|
|
ec86d26ae6 | ||
|
|
e00efc821a | ||
|
|
f04be8ca93 | ||
|
|
c7ff6a323a | ||
|
|
34833416bc | ||
|
|
d7a6e28d81 | ||
|
|
c13531d720 | ||
|
|
4a40cced7d | ||
|
|
450de10cc7 | ||
|
|
5e587a455c | ||
|
|
04aef9b258 | ||
|
|
9b72557d13 | ||
|
|
a5e0bc30c1 | ||
|
|
458c166e9f | ||
|
|
3373e5d141 | ||
|
|
8f6b8de617 | ||
|
|
22b21a82e7 | ||
|
|
b4151b299f | ||
|
|
e969fca8c4 | ||
|
|
e59993a5d6 | ||
|
|
3adc9d4cf1 | ||
|
|
c1153250cb | ||
|
|
7718dc28d3 | ||
|
|
1894580ae6 | ||
|
|
11df714bbf | ||
|
|
72adee37a8 | ||
|
|
0e8f460d73 | ||
|
|
4e62d5d0d2 | ||
|
|
004e5ba457 | ||
|
|
f1a8a8da6f | ||
|
|
bfb44d1189 | ||
|
|
d64afd8d97 | ||
|
|
97cfdb9c4c | ||
|
|
bb336f63f1 | ||
|
|
80da08f447 | ||
|
|
a6db2b7c90 | ||
|
|
e1c3f83324 | ||
|
|
8545a44ad1 | ||
|
|
cfbe7510a2 | ||
|
|
1479c32edb | ||
|
|
a9d428bf5a | ||
|
|
3a62eb11da | ||
|
|
2df5f81029 | ||
|
|
b88c23bc92 | ||
|
|
4144fbaf5b | ||
|
|
86ddc93f52 | ||
|
|
8a1b583535 | ||
|
|
7623628b86 | ||
|
|
f753bc203f | ||
|
|
ff9ba10705 | ||
|
|
fdc25bdc26 | ||
|
|
d154a587ad | ||
|
|
a9cc4f037b | ||
|
|
50d6679189 | ||
|
|
b82cf4e1d6 | ||
|
|
fa6eaa6710 | ||
|
|
2ca17ac1bd | ||
|
|
862ecd7d36 | ||
|
|
e38b72593b | ||
|
|
81883cae8f | ||
|
|
63770c9cbf | ||
|
|
aaf5da55a0 | ||
|
|
8455d9ae17 | ||
|
|
839a8e8eb4 | ||
|
|
7aa3f6eaee | ||
|
|
219abc69a3 | ||
|
|
595822c928 | ||
|
|
32112937b1 | ||
|
|
a0889c5b99 | ||
|
|
4bc281bfb0 | ||
|
|
e12ec607d7 | ||
|
|
545f393293 | ||
|
|
4efa152e93 | ||
|
|
0ebda4f32a | ||
|
|
b94f54799f | ||
|
|
99f0b4e2e2 | ||
|
|
53cb906733 | ||
|
|
2c31fe8a84 | ||
|
|
6c5e673a2c | ||
|
|
574b891a33 | ||
|
|
9ef1d3f58b | ||
|
|
8b3f432746 | ||
|
|
14a9df261e | ||
|
|
5620a2847b | ||
|
|
13aaae3835 | ||
|
|
2d4fcd3a4f | ||
|
|
f4f70b0bc2 | ||
|
|
1d279bb88f | ||
|
|
fe4757cbf9 | ||
|
|
1d476f163e | ||
|
|
513701b6d2 | ||
|
|
1a1b661714 | ||
|
|
6081a98279 | ||
|
|
157991c33d | ||
|
|
ef67be9170 | ||
|
|
39f3819303 | ||
|
|
7a676bd563 | ||
|
|
80c54daa03 | ||
|
|
99c88cde62 | ||
|
|
17d113ca20 | ||
|
|
2fbf4324a7 | ||
|
|
b46c390d30 | ||
|
|
3c3651bc52 | ||
|
|
abeeb5e66d | ||
|
|
09d61ce4c8 | ||
|
|
cc1c4495b2 | ||
|
|
7b1ec4d17f | ||
|
|
99b3201ccf | ||
|
|
d4205d9ba6 | ||
|
|
8d57633390 |
96
CHANGELOG.md
Normal file
96
CHANGELOG.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Change Log
|
||||
|
||||
## [1.2.1](https://github.com/zammad/zammad/tree/1.2.1) (2017-02-15)
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/1.2.0...1.2.1)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Ticket without Subject is blocked - can't do anything with it \("Title needed"\) [\#719](https://github.com/zammad/zammad/issues/719) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Unable to process emails with email addresses longer then 140 signs. [\#650](https://github.com/zammad/zammad/issues/650) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Unable to process emails if "Additional follow-up detection" was checked and unchecked again [\#740](https://github.com/zammad/zammad/issues/740) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Time accounting works only with dot [\#705](https://github.com/zammad/zammad/issues/705) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- "Close Tab" & "Next in Overview" don't work [\#730](https://github.com/zammad/zammad/issues/730) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Prevent attachment preview in browser attachment download [\#617](https://github.com/zammad/zammad/issues/617) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Feedback form not working in Safari [\#685](https://github.com/zammad/zammad/issues/685) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Follow up detection not working if ticket\_hook\_position "none" is used. [\#686](https://github.com/zammad/zammad/issues/686) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Customer get forced to save time for time accounting in customer interface - only allow it for agents. [\#636](https://github.com/zammad/zammad/issues/636) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Unable to activate time accounting in admin interface. [\#633](https://github.com/zammad/zammad/issues/633) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Prevent attachment preview in browser attachment download [\#617](https://github.com/zammad/zammad/issues/617) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
## [1.2.0](https://github.com/zammad/zammad/tree/1.2.0) (2017-01-16)
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/1.1.0...1.2.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Feature "Time recording" [\#373](https://github.com/zammad/zammad/issues/373) [[feature](https://github.com/zammad/zammad/labels/feature)]
|
||||
- Proxy support [\#439](https://github.com/zammad/zammad/issues/439) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Improved call log workflow [\#517](https://github.com/zammad/zammad/pull/517) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] ([TeaMoe](https://github.com/TeaMoe))
|
||||
- Create ticket with customer [\#514](https://github.com/zammad/zammad/pull/514) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] ([TeaMoe](https://github.com/TeaMoe))
|
||||
- Versions françaises [\#578](https://github.com/zammad/zammad/pull/578) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[translation](https://github.com/zammad/zammad/labels/translation)] ([GeraldElbaze-Medias-Cite](https://github.com/GeraldElbaze-Medias-Cite))
|
||||
- French for Chat widget [\#580](https://github.com/zammad/zammad/issues/580) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Show other active agents in ticket detail view \(collision prevention\) [\#352](https://github.com/zammad/zammad/issues/352) [[feature](https://github.com/zammad/zammad/labels/feature)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- OTRS import - long content type shows warning and gets cut [\#582](https://github.com/zammad/zammad/issues/582) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Setup asks for FQDN, but doesn't write to DB [\#462](https://github.com/zammad/zammad/issues/462) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Group restrictions not respected in slack notification [\#587](https://github.com/zammad/zammad/issues/587) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Keyboard shortcuts dialog breaks url bar [\#530](https://github.com/zammad/zammad/issues/530) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- OTRS import - unsupported DynamicField type fields cause import to abort [\#565](https://github.com/zammad/zammad/issues/565) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Gitlab Auth not working [\#596](https://github.com/zammad/zammad/issues/596) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Admin user login via Oauth [\#602](https://github.com/zammad/zammad/issues/602) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- If "new ticket created" popover is shown you can't click search box [\#597](https://github.com/zammad/zammad/issues/597) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- New ticket attributes are not shown for customer [\#576](https://github.com/zammad/zammad/issues/576) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Imap sort errors with gmail in production.log [\#568](https://github.com/zammad/zammad/issues/568) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Agent - Group assignment shows "\<br\>" [\#566](https://github.com/zammad/zammad/issues/566) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Renaming standard roles breaks ticket views [\#499](https://github.com/zammad/zammad/issues/499) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Overview is not show it organization is used as row and first ticket has no organization \(app is not responding anymore\) [\#554](https://github.com/zammad/zammad/issues/554) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Add organization to overview in manage users \(admin interface\) [\#552](https://github.com/zammad/zammad/issues/552) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- OTRS import: Empty created\_at of customer user record brakes import. [\#548](https://github.com/zammad/zammad/issues/548) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Layout broken if outbound channel has a error message [\#547](https://github.com/zammad/zammad/issues/547) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Do not show arrow down on ajax auto completion fields [\#546](https://github.com/zammad/zammad/issues/546) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Error message at creation of new overview with not existing specific customer [\#545](https://github.com/zammad/zammad/issues/545) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
|
||||
## [1.1.0](https://github.com/zammad/zammad/tree/1.1.0) (2016-11-14)
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/1.0.1...1.1.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Missing translation/edit abilities \(Notification mails, contact form\) [\#388](https://github.com/zammad/zammad/issues/388) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Automatically join users to organizations based on the email address [\#356](https://github.com/zammad/zammad/issues/356) [[feature](https://github.com/zammad/zammad/labels/feature)]
|
||||
- Create emails via fetchmail / procmail [\#326](https://github.com/zammad/zammad/issues/326) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Display origin url from tweet/facebook post [\#317](https://github.com/zammad/zammad/issues/317) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Vagrant version [\#253](https://github.com/zammad/zammad/issues/253) [[feature](https://github.com/zammad/zammad/labels/feature)]
|
||||
- Page posts from facebook and tweets from twitter as tickets [\#251](https://github.com/zammad/zammad/issues/251) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Some windows in the config area too wide in firefox [\#392](https://github.com/zammad/zammad/issues/392) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- \[Critical\] Short display of "Setup a new system" on public site while restarting Zammad! [\#389](https://github.com/zammad/zammad/issues/389) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Mailresponse to customer not working at all after editing a "Trigger" event text [\#385](https://github.com/zammad/zammad/issues/385) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Click on "Reports" leads to Error 500 [\#384](https://github.com/zammad/zammad/issues/384) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- TicketSelector in Overview or SLA not working with filter for ticket customer or owner [\#383](https://github.com/zammad/zammad/issues/383) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Changelog generator breaks packager.io builds [\#379](https://github.com/zammad/zammad/issues/379) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- script/scheduler.rb crashes after recreation of Zammad database [\#374](https://github.com/zammad/zammad/issues/374) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- ticket search cache issue [\#349](https://github.com/zammad/zammad/issues/349) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- IMAP mail fetching stops because of broken spam email \(invalid Content-Transfer-Encoding header\) [\#348](https://github.com/zammad/zammad/issues/348) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- IMAP mail fetching stops working on spam mail and invalid email format [\#345](https://github.com/zammad/zammad/issues/345) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Unable to create tickets [\#323](https://github.com/zammad/zammad/issues/323) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Admin -\> Channels -\> Email -\> Filters not shown / no name shown [\#313](https://github.com/zammad/zammad/issues/313) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- In object manager, blank line in object deleted all values for selections [\#312](https://github.com/zammad/zammad/issues/312) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- triggers do not work? [\#306](https://github.com/zammad/zammad/issues/306) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Answers from google mail shows complete quote [\#286](https://github.com/zammad/zammad/issues/286) [[minor bug](https://github.com/zammad/zammad/labels/minor%20bug)]
|
||||
- Avatar in Chat not showing - wrong url [\#275](https://github.com/zammad/zammad/issues/275) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- chat not working / http 404 [\#274](https://github.com/zammad/zammad/issues/274) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Zammad Docker container for rpi/ARM [\#271](https://github.com/zammad/zammad/issues/271) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Zendesk: unknown article type fails import. [\#270](https://github.com/zammad/zammad/issues/270) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- No space at comment on ticket bulk action [\#268](https://github.com/zammad/zammad/issues/268) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- After adding a new attribute - zammad won't start [\#248](https://github.com/zammad/zammad/issues/248) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Images/Avatars blank [\#217](https://github.com/zammad/zammad/issues/217) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Docker/Ubuntu Not starting postgresql-9.6 service [\#213](https://github.com/zammad/zammad/issues/213) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Can´t choose Organization for Customer [\#211](https://github.com/zammad/zammad/issues/211) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- CentOS 7 HTTPS Error 404 - Not Found [\#210](https://github.com/zammad/zammad/issues/210) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
|
||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
||||
@@ -678,6 +678,7 @@ class App.ControllerModal extends App.Controller
|
||||
container: null
|
||||
buttonClass: 'btn--success'
|
||||
centerButtons: []
|
||||
leftButtons: []
|
||||
buttonClose: true
|
||||
buttonCancel: false
|
||||
buttonCancelClass: 'btn--text btn--subtle'
|
||||
@@ -729,6 +730,7 @@ class App.ControllerModal extends App.Controller
|
||||
buttonSubmit: @buttonSubmit
|
||||
buttonClass: @buttonClass
|
||||
centerButtons: @centerButtons
|
||||
leftButtons: @leftButtons
|
||||
)
|
||||
modal.find('.modal-body').html content
|
||||
if !@initRenderingDone
|
||||
|
||||
@@ -1096,6 +1096,11 @@ class BulkForm extends App.Controller
|
||||
return
|
||||
|
||||
ticket.load(ticket_update)
|
||||
|
||||
# if title is empty - ticket can't processed, set ?
|
||||
if _.isEmpty(ticket.title)
|
||||
ticket.title = '-'
|
||||
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@bulk_count_index++
|
||||
|
||||
@@ -660,8 +660,6 @@ class App.TicketZoom extends App.Controller
|
||||
@formEnable(e)
|
||||
return
|
||||
|
||||
taskAction = @$('.js-secondaryActionButtonLabel').data('type')
|
||||
|
||||
ticketParams = @formParam(@$('.edit'))
|
||||
|
||||
# validate ticket
|
||||
@@ -705,11 +703,9 @@ class App.TicketZoom extends App.Controller
|
||||
if !ticket['owner_id']
|
||||
ticket['owner_id'] = 1
|
||||
|
||||
# check if title exists
|
||||
if !ticket['title']
|
||||
alert( App.i18n.translateContent('Title needed') )
|
||||
@formEnable(e)
|
||||
return
|
||||
# if title is empty - ticket can't processed, set ?
|
||||
if _.isEmpty(ticket.title)
|
||||
ticket.title = '-'
|
||||
|
||||
# stop autosave
|
||||
@autosaveStop()
|
||||
@@ -763,6 +759,10 @@ class App.TicketZoom extends App.Controller
|
||||
return
|
||||
|
||||
# time tracking
|
||||
if @permissionCheck('ticket.customer')
|
||||
@submitPost(e, ticket)
|
||||
return
|
||||
|
||||
new App.TicketZoomTimeAccounting(
|
||||
container: @el.closest('.content')
|
||||
ticket: ticket
|
||||
@@ -776,6 +776,8 @@ class App.TicketZoom extends App.Controller
|
||||
|
||||
submitPost: (e, ticket) =>
|
||||
|
||||
taskAction = @$('.js-secondaryActionButtonLabel').data('type')
|
||||
|
||||
# submit changes
|
||||
@ajax(
|
||||
id: "ticket_update_#{ticket.id}"
|
||||
|
||||
@@ -1,20 +1,47 @@
|
||||
class App.TicketZoomTimeAccounting extends App.ControllerModal
|
||||
buttonClose: true
|
||||
buttonCancel: 'skip'
|
||||
buttonCancel: false
|
||||
buttonSubmit: 'Account Time'
|
||||
buttonClass: 'btn--success'
|
||||
leftButtons: [{
|
||||
className: 'btn--text btn--subtle js-skip',
|
||||
text: 'skip'
|
||||
}]
|
||||
head: 'Time Accounting'
|
||||
small: true
|
||||
|
||||
events:
|
||||
'submit form': 'submit'
|
||||
'click .js-submit:not(.is-disabled)': 'submit'
|
||||
'click .js-cancel': 'cancel'
|
||||
'click .js-close': 'cancel'
|
||||
'click .js-skip': 'skip'
|
||||
|
||||
content: ->
|
||||
App.view('ticket_zoom/time_accounting')()
|
||||
|
||||
onShown: =>
|
||||
@$('[name="time_unit"]').focus()
|
||||
|
||||
skip: (e) =>
|
||||
return if !@submitCallback
|
||||
@submitCallback({})
|
||||
@close(e)
|
||||
|
||||
onCancel: =>
|
||||
if @cancelCallback
|
||||
@cancelCallback()
|
||||
return if !@cancelCallback
|
||||
@cancelCallback()
|
||||
|
||||
onSubmit: =>
|
||||
@$('[name="time_unit"]').removeClass('has-error')
|
||||
params = @formParams()
|
||||
if params.time_unit
|
||||
params.time_unit = params.time_unit.replace(',', '.')
|
||||
|
||||
if isNaN(parseFloat(params.time_unit)) || params.time_unit.match(/[A-z]|\s/)
|
||||
@$('[name="time_unit"]').addClass('has-error')
|
||||
return
|
||||
|
||||
@close()
|
||||
if @submitCallback
|
||||
params = @formParams()
|
||||
@submitCallback(params)
|
||||
|
||||
@@ -282,9 +282,12 @@ class _i18nSingleton extends Spine.Module
|
||||
notTranslated
|
||||
|
||||
removeNotTranslated: (locale, key) =>
|
||||
return if !@_notTranslated[locale]
|
||||
delete @_notTranslated[locale][key]
|
||||
|
||||
setNotTranslated: (locale, key) =>
|
||||
if !@_notTranslated[locale]
|
||||
@_notTranslated[locale] = {}
|
||||
@_notTranslated[locale][key] = true
|
||||
|
||||
date: (time, offset) =>
|
||||
|
||||
@@ -15,8 +15,14 @@
|
||||
<div class="modal-footer">
|
||||
<% if @buttonCancel: %>
|
||||
<div class="modal-leftFooter">
|
||||
<a class="btn <%= @buttonCancelClass %> js-cancel align-left" href="#/"><%- @T(@buttonCancel) %></a>
|
||||
<a class="btn <%= @buttonCancelClass %> js-cancel align-left" href="#"><%- @T(@buttonCancel) %></a>
|
||||
</div>
|
||||
<% else if @leftButtons: %>
|
||||
<% for button in @leftButtons: %>
|
||||
<div class="modal-leftFooter">
|
||||
<div class="btn <%= button.className %> align-left" href="#"><%- @T(button.text) %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% for button in @centerButtons: %>
|
||||
<div class="modal-centerFooter">
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="attachments-title"><%- @article.attachments.length %> <%- @T('Attached Files') %></div>
|
||||
<% for attachment in @article.attachments: %>
|
||||
<div class="attachment">
|
||||
<a class="attachment-name u-highlight" href="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>" target="_blank" data-type="attachment"><%= attachment.filename %></a>
|
||||
<a class="attachment-name u-highlight" href="<%= App.Config.get('api_path') %>/ticket_attachment/<%= @article.ticket_id %>/<%= @article.id %>/<%= attachment.id %>?disposition=attachment" target="_blank" data-type="attachment"><%= attachment.filename %></a>
|
||||
<div class="attachment-size"><%- @humanFileSize(attachment.size) %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -239,11 +239,14 @@ class TicketArticlesController < ApplicationController
|
||||
|
||||
# find file
|
||||
file = Store.find(params[:id])
|
||||
|
||||
disposition = sanitized_disposition
|
||||
|
||||
send_data(
|
||||
file.content,
|
||||
filename: file.filename,
|
||||
type: file.preferences['Content-Type'] || file.preferences['Mime-Type'],
|
||||
disposition: 'inline'
|
||||
disposition: disposition
|
||||
)
|
||||
end
|
||||
|
||||
@@ -267,4 +270,12 @@ class TicketArticlesController < ApplicationController
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sanitized_disposition
|
||||
disposition = params.fetch(:disposition, 'inline')
|
||||
valid_disposition = %w(inline attachment)
|
||||
return disposition if valid_disposition.include?(disposition)
|
||||
raise Exceptions::NotAuthorized, "Invalid disposition #{disposition} requested. Only #{valid_disposition.join(', ')} are valid."
|
||||
end
|
||||
end
|
||||
|
||||
@@ -212,6 +212,9 @@ returns
|
||||
next if !respond_to?(real_ids)
|
||||
attributes[real_ids] = send(real_ids)
|
||||
}
|
||||
|
||||
filter_attributes(attributes)
|
||||
|
||||
Cache.write(key, attributes)
|
||||
attributes
|
||||
end
|
||||
@@ -271,12 +274,16 @@ returns
|
||||
attributes[destination] = user.login
|
||||
}
|
||||
|
||||
filter_attributes(attributes)
|
||||
|
||||
attributes
|
||||
end
|
||||
|
||||
def filter_attributes(attributes)
|
||||
# remove forbitten attributes
|
||||
%w(password token tokens token_ids).each { |item|
|
||||
attributes.delete(item)
|
||||
}
|
||||
|
||||
attributes
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
@@ -79,14 +79,14 @@ class Channel::EmailParser
|
||||
data[field.name.to_s.downcase.to_sym] = Encode.conv('utf8', field.to_s)
|
||||
|
||||
# if we need to access the lines by objects later again
|
||||
data[ "raw-#{field.name.downcase}".to_sym ] = field
|
||||
data["raw-#{field.name.downcase}".to_sym] = field
|
||||
}
|
||||
|
||||
# get sender
|
||||
from = nil
|
||||
['from', 'reply-to', 'return-path'].each { |item|
|
||||
next if !mail[ item.to_sym ]
|
||||
from = mail[ item.to_sym ].value
|
||||
next if !mail[item.to_sym]
|
||||
from = mail[item.to_sym].value
|
||||
break if from
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ returns
|
||||
do not raise an exception - e. g. if used by scheduler
|
||||
|
||||
parser = Channel::EmailParser.new
|
||||
ticket, article, user = parser.process(channel, email_raw_string, fakse)
|
||||
ticket, article, user = parser.process(channel, email_raw_string, false)
|
||||
|
||||
returns
|
||||
|
||||
@@ -430,7 +430,7 @@ returns
|
||||
}
|
||||
|
||||
# check ignore header
|
||||
if mail[ 'x-zammad-ignore'.to_sym ] == 'true' || mail[ 'x-zammad-ignore'.to_sym ] == true
|
||||
if mail['x-zammad-ignore'.to_sym] == 'true' || mail['x-zammad-ignore'.to_sym] == true
|
||||
Rails.logger.info "ignored email with msgid '#{mail[:message_id]}' from '#{mail[:from]}' because of x-zammad-ignore header"
|
||||
return true
|
||||
end
|
||||
@@ -446,7 +446,7 @@ returns
|
||||
Transaction.execute(interface_handle: "#{original_interface_handle}.postmaster") do
|
||||
|
||||
# get sender user
|
||||
session_user_id = mail[ 'x-zammad-session-user-id'.to_sym ]
|
||||
session_user_id = mail['x-zammad-session-user-id'.to_sym]
|
||||
if !session_user_id
|
||||
raise 'No x-zammad-session-user-id, no sender set!'
|
||||
end
|
||||
@@ -459,11 +459,11 @@ returns
|
||||
UserInfo.current_user_id = session_user.id
|
||||
|
||||
# get ticket# based on email headers
|
||||
if mail[ 'x-zammad-ticket-id'.to_sym ]
|
||||
ticket = Ticket.find_by(id: mail[ 'x-zammad-ticket-id'.to_sym ])
|
||||
if mail['x-zammad-ticket-id'.to_sym]
|
||||
ticket = Ticket.find_by(id: mail['x-zammad-ticket-id'.to_sym])
|
||||
end
|
||||
if mail[ 'x-zammad-ticket-number'.to_sym ]
|
||||
ticket = Ticket.find_by(number: mail[ 'x-zammad-ticket-number'.to_sym ])
|
||||
if mail['x-zammad-ticket-number'.to_sym]
|
||||
ticket = Ticket.find_by(number: mail['x-zammad-ticket-number'.to_sym])
|
||||
end
|
||||
|
||||
# set ticket state to open if not new
|
||||
@@ -482,8 +482,8 @@ returns
|
||||
end
|
||||
|
||||
# set ticket to open again
|
||||
if !mail[ 'x-zammad-ticket-followup-state'.to_sym ]
|
||||
if state_type.name != 'new' && !mail[ 'x-zammad-out-of-office'.to_sym ]
|
||||
if !mail['x-zammad-ticket-followup-state'.to_sym] && !mail['x-zammad-ticket-followup-state_id'.to_sym]
|
||||
if state_type.name != 'new' && !mail['x-zammad-out-of-office'.to_sym]
|
||||
ticket.state = Ticket::State.find_by(name: 'open')
|
||||
ticket.save!
|
||||
end
|
||||
@@ -626,25 +626,25 @@ returns
|
||||
|
||||
# only set value on _id if value/reference lookup exists
|
||||
if mail[ header.to_sym ]
|
||||
Rails.logger.info "header #{header} found #{mail[ header.to_sym ]}"
|
||||
Rails.logger.info "header #{header} found #{mail[header.to_sym]}"
|
||||
item_object.class.reflect_on_all_associations.map { |assoc|
|
||||
|
||||
next if assoc.name.to_s != key_short
|
||||
|
||||
Rails.logger.info "ASSOC found #{assoc.class_name} lookup #{mail[ header.to_sym ]}"
|
||||
Rails.logger.info "ASSOC found #{assoc.class_name} lookup #{mail[header.to_sym]}"
|
||||
item = assoc.class_name.constantize
|
||||
|
||||
assoc_object = nil
|
||||
assoc_has_object = false
|
||||
if item.respond_to?(:name)
|
||||
assoc_has_object = true
|
||||
if item.lookup(name: mail[ header.to_sym ])
|
||||
assoc_object = item.lookup(name: mail[ header.to_sym ])
|
||||
if item.lookup(name: mail[header.to_sym])
|
||||
assoc_object = item.lookup(name: mail[header.to_sym])
|
||||
end
|
||||
elsif item.respond_to?(:login)
|
||||
assoc_has_object = true
|
||||
if item.lookup(login: mail[ header.to_sym ])
|
||||
assoc_object = item.lookup(login: mail[ header.to_sym ])
|
||||
if item.lookup(login: mail[header.to_sym])
|
||||
assoc_object = item.lookup(login: mail[header.to_sym])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -666,9 +666,9 @@ returns
|
||||
if suffix
|
||||
header = "x-zammad-#{header_name}-#{suffix}-#{key}"
|
||||
end
|
||||
if mail[ header.to_sym ]
|
||||
Rails.logger.info "header #{header} found #{mail[ header.to_sym ]}"
|
||||
item_object[key] = mail[ header.to_sym ]
|
||||
if mail[header.to_sym]
|
||||
Rails.logger.info "header #{header} found #{mail[header.to_sym]}"
|
||||
item_object[key] = mail[header.to_sym]
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ module Channel::Filter::FollowUpCheck
|
||||
return true
|
||||
end
|
||||
|
||||
setting = Setting.get('postmaster_follow_up_search_in')
|
||||
setting = Setting.get('postmaster_follow_up_search_in') || []
|
||||
|
||||
# get ticket# from body
|
||||
if setting.include?('body')
|
||||
@@ -39,7 +39,7 @@ module Channel::Filter::FollowUpCheck
|
||||
end
|
||||
|
||||
# get ticket# from references
|
||||
if setting.include?('references') || mail[ 'x-zammad-is-auto-response'.to_sym ] == true
|
||||
if setting.include?('references') || (mail[ 'x-zammad-is-auto-response'.to_sym ] == true || Setting.get('ticket_hook_position') == 'none')
|
||||
|
||||
# get all references 'References' + 'In-Reply-To'
|
||||
references = ''
|
||||
@@ -66,7 +66,7 @@ module Channel::Filter::FollowUpCheck
|
||||
end
|
||||
|
||||
# get ticket# from references current email has same subject as inital article
|
||||
if !mail[:subject].empty?
|
||||
if mail[:subject].present?
|
||||
|
||||
# get all references 'References' + 'In-Reply-To'
|
||||
references = ''
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
class Chat::Message < ApplicationModel
|
||||
include HtmlSanitized
|
||||
|
||||
sanitized_html :content
|
||||
end
|
||||
|
||||
46
app/models/concerns/html_sanitized.rb
Normal file
46
app/models/concerns/html_sanitized.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
module HtmlSanitized
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_create :sanitized_html_attributes
|
||||
before_update :sanitized_html_attributes
|
||||
end
|
||||
|
||||
def sanitized_html_attributes
|
||||
html_attributes = self.class.instance_variable_get(:@sanitized_html) || []
|
||||
return if html_attributes.empty?
|
||||
|
||||
html_attributes.each do |attribute|
|
||||
value = send(attribute)
|
||||
|
||||
next if value.blank?
|
||||
next if !sanitizeable?(attribute, value)
|
||||
|
||||
send("#{attribute}=".to_sym, HtmlSanitizer.strict(value))
|
||||
end
|
||||
end
|
||||
|
||||
def sanitizeable?(_attribute, _value)
|
||||
true
|
||||
end
|
||||
|
||||
# methods defined here are going to extend the class, not the instance of it
|
||||
class_methods do
|
||||
|
||||
=begin
|
||||
|
||||
serve methode to mark HTML attrbibutes that need to get sanitized
|
||||
|
||||
class Model < ApplicationModel
|
||||
include Sanitized
|
||||
sanitized_html :body
|
||||
end
|
||||
|
||||
=end
|
||||
|
||||
def sanitized_html(*attributes)
|
||||
@sanitized_html = attributes
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,7 @@
|
||||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
class Ticket::Article < ApplicationModel
|
||||
include HtmlSanitized
|
||||
|
||||
load 'ticket/article/assets.rb'
|
||||
include Ticket::Article::Assets
|
||||
load 'ticket/article/history_log.rb'
|
||||
@@ -16,6 +18,8 @@ class Ticket::Article < ApplicationModel
|
||||
before_create :check_subject, :check_message_id_md5
|
||||
before_update :check_subject, :check_message_id_md5
|
||||
|
||||
sanitized_html :body
|
||||
|
||||
notify_clients_support
|
||||
|
||||
activity_stream_support(
|
||||
@@ -212,6 +216,12 @@ returns:
|
||||
)
|
||||
end
|
||||
|
||||
def sanitizeable?(attribute, _value)
|
||||
return true if attribute != :body
|
||||
return false if content_type.blank?
|
||||
content_type =~ /html/i
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# strip not wanted chars
|
||||
|
||||
48
config/initializers/html_sanitizer.rb
Normal file
48
config/initializers/html_sanitizer.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
# content of this tags will also be removed
|
||||
Rails.application.config.html_sanitizer_tags_remove_content = %w(
|
||||
style
|
||||
)
|
||||
|
||||
# only this tags are allowed
|
||||
Rails.application.config.html_sanitizer_tags_whitelist = %w(
|
||||
a abbr acronym address area article aside audio
|
||||
b bdi bdo big blockquote br
|
||||
canvas caption center cite code col colgroup command
|
||||
datalist dd del details dfn dir div dl dt em
|
||||
figcaption figure footer h1 h2 h3 h4 h5 h6 header hr
|
||||
i img ins kbd label legend li map mark menu meter nav
|
||||
ol output optgroup option p pre q
|
||||
s samp section small span strike strong sub summary sup
|
||||
text table tbody td tfoot th thead time tr tt u ul var video
|
||||
)
|
||||
|
||||
# attributes allowed for tags
|
||||
Rails.application.config.html_sanitizer_attributes_whitelist = {
|
||||
:all => %w(class dir lang style title translate data-signature data-signature-id),
|
||||
'a' => %w(href hreflang name rel),
|
||||
'abbr' => %w(title),
|
||||
'blockquote' => %w(cite),
|
||||
'col' => %w(span width),
|
||||
'colgroup' => %w(span width),
|
||||
'data' => %w(value),
|
||||
'del' => %w(cite datetime),
|
||||
'dfn' => %w(title),
|
||||
'img' => %w(align alt border height src srcset width),
|
||||
'ins' => %w(cite datetime),
|
||||
'li' => %w(value),
|
||||
'ol' => %w(reversed start type),
|
||||
'table' => %w(align bgcolor border cellpadding cellspacing frame rules sortable summary width),
|
||||
'td' => %w(abbr align axis colspan headers rowspan valign width),
|
||||
'th' => %w(abbr align axis colspan headers rowspan scope sorted valign width),
|
||||
'ul' => %w(type),
|
||||
'q' => %w(cite),
|
||||
'time' => %w(datetime pubdate),
|
||||
}
|
||||
|
||||
# only this css properties are allowed
|
||||
Rails.application.config.html_sanitizer_css_properties_whitelist = %w(
|
||||
width height
|
||||
max-width min-width
|
||||
max-height min-height
|
||||
)
|
||||
@@ -16,10 +16,10 @@ class CreateBase < ActiveRecord::Migration
|
||||
|
||||
create_table :users do |t|
|
||||
t.references :organization, null: true
|
||||
t.string :login, limit: 100, null: false
|
||||
t.string :login, limit: 255, null: false
|
||||
t.string :firstname, limit: 100, null: true, default: ''
|
||||
t.string :lastname, limit: 100, null: true, default: ''
|
||||
t.string :email, limit: 140, null: true, default: ''
|
||||
t.string :email, limit: 255, null: true, default: ''
|
||||
t.string :image, limit: 100, null: true
|
||||
t.string :image_source, limit: 200, null: true
|
||||
t.string :web, limit: 100, null: true, default: ''
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
class UnableToEnableTimeAccounting633 < ActiveRecord::Migration
|
||||
def up
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Time Accounting',
|
||||
name: 'time_accounting',
|
||||
area: 'Web::Base',
|
||||
description: 'Enable time accounting.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'time_accounting',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
preferences: {
|
||||
authentication: true,
|
||||
permission: ['admin.time_accounting'],
|
||||
},
|
||||
state: false,
|
||||
frontend: true
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Time Accounting Selector',
|
||||
name: 'time_accounting_selector',
|
||||
area: 'Web::Base',
|
||||
description: 'Enable time accounting for this tickets.',
|
||||
options: {
|
||||
form: [
|
||||
{},
|
||||
],
|
||||
},
|
||||
preferences: {
|
||||
authentication: true,
|
||||
permission: ['admin.time_accounting'],
|
||||
},
|
||||
state: {},
|
||||
frontend: true
|
||||
)
|
||||
end
|
||||
end
|
||||
10
db/migrate/20170119000001_login_email_length_650.rb
Normal file
10
db/migrate/20170119000001_login_email_length_650.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class LoginEmailLength650 < ActiveRecord::Migration
|
||||
def up
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
change_column(:users, :login, :string, limit: 255)
|
||||
change_column(:users, :email, :string, limit: 255)
|
||||
end
|
||||
end
|
||||
92
lib/html_sanitizer.rb
Normal file
92
lib/html_sanitizer.rb
Normal file
@@ -0,0 +1,92 @@
|
||||
class HtmlSanitizer
|
||||
|
||||
def self.strict(string)
|
||||
|
||||
# config
|
||||
tags_remove_content = Rails.configuration.html_sanitizer_tags_remove_content
|
||||
tags_whitelist = Rails.configuration.html_sanitizer_tags_whitelist
|
||||
attributes_whitelist = Rails.configuration.html_sanitizer_attributes_whitelist
|
||||
css_properties_whitelist = Rails.configuration.html_sanitizer_css_properties_whitelist
|
||||
|
||||
scrubber = Loofah::Scrubber.new do |node|
|
||||
|
||||
# remove tags with subtree
|
||||
if tags_remove_content.include?(node.name)
|
||||
node.remove
|
||||
end
|
||||
|
||||
# replace tags, keep subtree
|
||||
if !tags_whitelist.include?(node.name)
|
||||
traversal(node, scrubber)
|
||||
end
|
||||
|
||||
# prepare src attribute
|
||||
if node['src']
|
||||
src = cleanup(node['src'])
|
||||
if src =~ /(javascript|livescript|vbscript):/i || src.start_with?('http', 'ftp', '//')
|
||||
traversal(node, scrubber)
|
||||
end
|
||||
end
|
||||
|
||||
# clean style / only use allowed style properties
|
||||
if node['style']
|
||||
pears = node['style'].downcase.gsub(/\t|\n|\r/, '').split(';')
|
||||
style = ''
|
||||
pears.each { |pear|
|
||||
prop = pear.split(':')
|
||||
next if !prop[0]
|
||||
key = prop[0].strip
|
||||
next if !css_properties_whitelist.include?(key)
|
||||
style += "#{pear};"
|
||||
}
|
||||
node['style'] = style
|
||||
if style == ''
|
||||
node.delete('style')
|
||||
end
|
||||
end
|
||||
|
||||
# scan for invalid link content
|
||||
%w(href style).each { |attribute_name|
|
||||
next if !node[attribute_name]
|
||||
href = cleanup(node[attribute_name])
|
||||
next if href !~ /(javascript|livescript|vbscript):/i
|
||||
node.delete(attribute_name)
|
||||
}
|
||||
|
||||
# remove attributes if not whitelisted
|
||||
node.each { |attribute, _value|
|
||||
attribute_name = attribute.downcase
|
||||
next if attributes_whitelist[:all].include?(attribute_name) || (attributes_whitelist[node.name] && attributes_whitelist[node.name].include?(attribute_name))
|
||||
node.delete(attribute)
|
||||
}
|
||||
|
||||
# prepare links
|
||||
if node['href']
|
||||
href = cleanup(node['href'])
|
||||
next if !href.start_with?('http', 'ftp', '//')
|
||||
node.set_attribute('rel', 'nofollow')
|
||||
node.set_attribute('target', '_blank')
|
||||
end
|
||||
end
|
||||
Loofah.fragment(string).scrub!(scrubber).to_s
|
||||
end
|
||||
|
||||
def self.traversal(node, scrubber)
|
||||
node.children.each { |child|
|
||||
if child.class == Nokogiri::XML::CDATA
|
||||
node.before Nokogiri::XML::Text.new(node.content, node.document)
|
||||
else
|
||||
node.before Loofah.fragment(child.to_s).scrub!(scrubber)
|
||||
end
|
||||
}
|
||||
node.remove
|
||||
end
|
||||
|
||||
def self.cleanup(string)
|
||||
string.downcase.gsub(/[[:space:]]|\t|\n|\r/, '').gsub(%r{/\*.*?\*/}, '').gsub(/<!--.*?-->/, '').gsub(/\[.+?\]/, '')
|
||||
end
|
||||
|
||||
private_class_method :traversal
|
||||
private_class_method :cleanup
|
||||
|
||||
end
|
||||
@@ -32,6 +32,7 @@ returns
|
||||
next if entry =~ %r{channel/}i
|
||||
next if entry =~ %r{observer/}i
|
||||
next if entry =~ %r{store/provider/}i
|
||||
next if entry =~ %r{models/concerns/}i
|
||||
entry.gsub!(dir, '')
|
||||
entry = entry.to_classname
|
||||
model_class = load_adapter(entry)
|
||||
|
||||
@@ -277,9 +277,14 @@ $(function() {
|
||||
|
||||
var formData = new FormData(_this.$form[0])
|
||||
|
||||
/* unfortunaly not working in safari and some IEs - https://developer.mozilla.org/en-US/docs/Web/API/FormData
|
||||
if (!formData.has('title')) {
|
||||
formData.append('title', this.options.messageTitle)
|
||||
}
|
||||
*/
|
||||
if (!_this.$form.find('[name=title]').val()) {
|
||||
formData.append('title', this.options.messageTitle)
|
||||
}
|
||||
|
||||
if (this.options.test) {
|
||||
formData.append('test', true)
|
||||
|
||||
@@ -28,8 +28,10 @@ if [ "$LEVEL" == '1' ]; then
|
||||
rm test/browser/agent_ticket_online_notification_test.rb
|
||||
rm test/browser/agent_ticket_overview_level0_test.rb
|
||||
rm test/browser/agent_ticket_overview_level1_test.rb
|
||||
rm test/browser/agent_ticket_overview_tab_test.rb
|
||||
rm test/browser/agent_ticket_tag_test.rb
|
||||
rm test/browser/agent_ticket_text_module_test.rb
|
||||
rm test/browser/agent_ticket_time_accounting_test.rb
|
||||
rm test/browser/agent_ticket_update1_test.rb
|
||||
rm test/browser/agent_ticket_update2_test.rb
|
||||
rm test/browser/agent_ticket_update3_test.rb
|
||||
@@ -83,8 +85,10 @@ elif [ "$LEVEL" == '2' ]; then
|
||||
rm test/browser/agent_ticket_online_notification_test.rb
|
||||
rm test/browser/agent_ticket_overview_level0_test.rb
|
||||
rm test/browser/agent_ticket_overview_level1_test.rb
|
||||
rm test/browser/agent_ticket_overview_tab_test.rb
|
||||
rm test/browser/agent_ticket_tag_test.rb
|
||||
# test/browser/agent_ticket_text_module_test.rb
|
||||
# test/browser/agent_ticket_time_accounting_test.rb
|
||||
# test/browser/agent_ticket_update1_test.rb
|
||||
# test/browser/agent_ticket_update2_test.rb
|
||||
# test/browser/agent_ticket_update3_test.rb
|
||||
@@ -138,8 +142,10 @@ elif [ "$LEVEL" == '3' ]; then
|
||||
rm test/browser/agent_ticket_online_notification_test.rb
|
||||
rm test/browser/agent_ticket_overview_level0_test.rb
|
||||
rm test/browser/agent_ticket_overview_level1_test.rb
|
||||
rm test/browser/agent_ticket_overview_tab_test.rb
|
||||
# test/browser/agent_ticket_tag_test.rb
|
||||
rm test/browser/agent_ticket_text_module_test.rb
|
||||
rm test/browser/agent_ticket_time_accounting_test.rb
|
||||
rm test/browser/agent_ticket_update1_test.rb
|
||||
rm test/browser/agent_ticket_update2_test.rb
|
||||
rm test/browser/agent_ticket_update3_test.rb
|
||||
@@ -193,8 +199,10 @@ elif [ "$LEVEL" == '4' ]; then
|
||||
# test/browser/agent_ticket_online_notification_test.rb
|
||||
# test/browser/agent_ticket_overview_level0_test.rb
|
||||
# test/browser/agent_ticket_overview_level1_test.rb
|
||||
# test/browser/agent_ticket_overview_tab_test.rb
|
||||
rm test/browser/agent_ticket_tag_test.rb
|
||||
rm test/browser/agent_ticket_text_module_test.rb
|
||||
rm test/browser/agent_ticket_time_accounting_test.rb
|
||||
rm test/browser/agent_ticket_update1_test.rb
|
||||
rm test/browser/agent_ticket_update2_test.rb
|
||||
rm test/browser/agent_ticket_update3_test.rb
|
||||
@@ -247,8 +255,10 @@ elif [ "$LEVEL" == '5' ]; then
|
||||
rm test/browser/agent_ticket_online_notification_test.rb
|
||||
rm test/browser/agent_ticket_overview_level0_test.rb
|
||||
rm test/browser/agent_ticket_overview_level1_test.rb
|
||||
rm test/browser/agent_ticket_overview_tab_test.rb
|
||||
rm test/browser/agent_ticket_tag_test.rb
|
||||
rm test/browser/agent_ticket_text_module_test.rb
|
||||
rm test/browser/agent_ticket_time_accounting_test.rb
|
||||
rm test/browser/agent_ticket_update1_test.rb
|
||||
rm test/browser/agent_ticket_update2_test.rb
|
||||
rm test/browser/agent_ticket_update3_test.rb
|
||||
@@ -304,8 +314,10 @@ elif [ "$LEVEL" == '6' ]; then
|
||||
rm test/browser/agent_ticket_online_notification_test.rb
|
||||
rm test/browser/agent_ticket_overview_level0_test.rb
|
||||
rm test/browser/agent_ticket_overview_level1_test.rb
|
||||
rm test/browser/agent_ticket_overview_tab_test.rb
|
||||
rm test/browser/agent_ticket_tag_test.rb
|
||||
rm test/browser/agent_ticket_text_module_test.rb
|
||||
rm test/browser/agent_ticket_time_accounting_test.rb
|
||||
rm test/browser/agent_ticket_update1_test.rb
|
||||
rm test/browser/agent_ticket_update2_test.rb
|
||||
rm test/browser/agent_ticket_update3_test.rb
|
||||
|
||||
87
test/browser/agent_ticket_overview_tab_test.rb
Normal file
87
test/browser/agent_ticket_overview_tab_test.rb
Normal file
@@ -0,0 +1,87 @@
|
||||
# encoding: utf-8
|
||||
require 'browser_test_helper'
|
||||
|
||||
class AgentTicketOverviewTabTest < TestCase
|
||||
def test_i
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
title = "test #{rand(9_999_999)}"
|
||||
|
||||
# create new ticket
|
||||
ticket1 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: "overview tab test #1 - #{title}",
|
||||
body: "overview tab test #1 - #{title}",
|
||||
}
|
||||
)
|
||||
ticket2 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: "overview tab test #2 - #{title}",
|
||||
body: "overview tab test #2 - #{title}",
|
||||
}
|
||||
)
|
||||
ticket3 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: "overview tab test #3 - #{title}",
|
||||
body: "overview tab test #3 - #{title}",
|
||||
}
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
#click(text: 'Overviews')
|
||||
# enable full overviews
|
||||
#execute(
|
||||
# js: '$(".content.active .sidebar").css("display", "block")',
|
||||
#)
|
||||
#click(text: 'Unassigned & Open')
|
||||
sleep 8 # till overview is rendered
|
||||
|
||||
ticket_open_by_overview(
|
||||
number: ticket1[:number],
|
||||
title: "overview tab test #1 - #{title}",
|
||||
link: '#ticket/view/all_unassigned',
|
||||
)
|
||||
|
||||
assert_equal(1, @browser.find_elements(css: '.tasks .task').count)
|
||||
|
||||
ticket_update(
|
||||
data: {
|
||||
body: 'some body',
|
||||
state: 'closed',
|
||||
},
|
||||
task_type: 'closeNextInOverview', # default: stayOnTab / possible: closeTab, closeNextInOverview, stayOnTab
|
||||
)
|
||||
|
||||
match(
|
||||
css: '.tasks .task.is-active',
|
||||
value: "overview tab test #2 - #{title}",
|
||||
)
|
||||
|
||||
assert_equal(1, @browser.find_elements(css: '.tasks .task').count)
|
||||
|
||||
ticket_update(
|
||||
data: {
|
||||
body: 'some body',
|
||||
state: 'closed',
|
||||
},
|
||||
task_type: 'closeTab', # default: stayOnTab / possible: closeTab, closeNextInOverview, stayOnTab
|
||||
)
|
||||
|
||||
assert_equal(0, @browser.find_elements(css: '.tasks .task').count)
|
||||
|
||||
# cleanup
|
||||
tasks_close_all()
|
||||
end
|
||||
end
|
||||
156
test/browser/agent_ticket_time_accounting_test.rb
Normal file
156
test/browser/agent_ticket_time_accounting_test.rb
Normal file
@@ -0,0 +1,156 @@
|
||||
# encoding: utf-8
|
||||
require 'browser_test_helper'
|
||||
|
||||
class AgentTicketTimeAccountingTest < TestCase
|
||||
def test_macro
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
# enable time accounting
|
||||
click(
|
||||
css: 'a[href="#manage"]',
|
||||
)
|
||||
click(
|
||||
css: '.content.active a[href="#manage/time_accounting"]',
|
||||
)
|
||||
switch(
|
||||
css: '.content.active .js-timeAccountingSetting',
|
||||
type: 'on',
|
||||
)
|
||||
|
||||
ticket1 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: 'some subject - time accounting#1',
|
||||
body: 'some body - time accounting#1',
|
||||
},
|
||||
)
|
||||
|
||||
ticket_update(
|
||||
data: {
|
||||
body: 'some note',
|
||||
},
|
||||
do_not_submit: true,
|
||||
)
|
||||
click(
|
||||
css: '.active .js-submit',
|
||||
)
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .modal',
|
||||
value: 'Time Accounting',
|
||||
)
|
||||
set(
|
||||
css: '.content.active .modal [name=time_unit]',
|
||||
value: '4',
|
||||
)
|
||||
click(
|
||||
css: '.content.active .modal .js-submit',
|
||||
)
|
||||
watch_for_disappear(
|
||||
css: '.content.active .modal',
|
||||
value: 'Time Accounting',
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active .js-timeUnit',
|
||||
value: '4',
|
||||
)
|
||||
|
||||
ticket2 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: 'some subject - time accounting#2',
|
||||
body: 'some body - time accounting#2',
|
||||
},
|
||||
)
|
||||
|
||||
ticket_update(
|
||||
data: {
|
||||
body: 'some note',
|
||||
},
|
||||
do_not_submit: true,
|
||||
)
|
||||
click(
|
||||
css: '.active .js-submit',
|
||||
)
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .modal',
|
||||
value: 'Time Accounting',
|
||||
)
|
||||
set(
|
||||
css: '.content.active .modal [name=time_unit]',
|
||||
value: '4,6',
|
||||
)
|
||||
click(
|
||||
css: '.content.active .modal .js-submit',
|
||||
)
|
||||
watch_for_disappear(
|
||||
css: '.content.active .modal',
|
||||
value: 'Time Accounting',
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active .js-timeUnit',
|
||||
value: '4.6',
|
||||
)
|
||||
|
||||
ticket_update(
|
||||
data: {
|
||||
body: 'some note2',
|
||||
},
|
||||
do_not_submit: true,
|
||||
)
|
||||
click(
|
||||
css: '.active .js-submit',
|
||||
)
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .modal',
|
||||
value: 'Time Accounting',
|
||||
)
|
||||
set(
|
||||
css: '.content.active .modal [name=time_unit]',
|
||||
value: '4abc',
|
||||
)
|
||||
click(
|
||||
css: '.content.active .modal .js-submit',
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active .modal [name=time_unit].has-error',
|
||||
)
|
||||
set(
|
||||
css: '.content.active .modal [name=time_unit]',
|
||||
value: '4 ',
|
||||
)
|
||||
click(
|
||||
css: '.content.active .modal .js-submit',
|
||||
)
|
||||
watch_for_disappear(
|
||||
css: '.content.active .modal',
|
||||
value: 'Time Accounting',
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active .js-timeUnit',
|
||||
value: '8.6',
|
||||
)
|
||||
|
||||
# disable time accounting
|
||||
click(
|
||||
css: 'a[href="#manage"]',
|
||||
)
|
||||
click(
|
||||
css: '.content.active a[href="#manage/time_accounting"]',
|
||||
)
|
||||
switch(
|
||||
css: '.content.active .js-timeAccountingSetting',
|
||||
type: 'off',
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -963,6 +963,8 @@ class TestCase < Test::Unit::TestCase
|
||||
|
||||
=begin
|
||||
|
||||
set type of task (closeTab, closeNextInOverview, stayOnTab)
|
||||
|
||||
task_type(
|
||||
browser: browser1,
|
||||
type: 'stayOnTab',
|
||||
@@ -975,7 +977,6 @@ class TestCase < Test::Unit::TestCase
|
||||
log('task_type', params)
|
||||
|
||||
instance = params[:browser] || @browser
|
||||
|
||||
if params[:type]
|
||||
instance.find_elements(css: '.content.active .js-secondaryActionButtonLabel')[0].click
|
||||
instance.find_elements(css: ".content.active .js-secondaryActionLabel[data-type=#{params[:type]}]")[0].click
|
||||
@@ -2032,6 +2033,7 @@ wait untill text in selector disabppears
|
||||
key1: 'some value',
|
||||
},
|
||||
do_not_submit: true,
|
||||
task_type: 'stayOnTab', # default: stayOnTab / possible: closeTab, closeNextInOverview, stayOnTab
|
||||
)
|
||||
|
||||
=end
|
||||
@@ -2203,14 +2205,13 @@ wait untill text in selector disabppears
|
||||
}
|
||||
if !found
|
||||
screenshot(browser: instance, comment: 'ticket_update_discard_message_failed')
|
||||
|
||||
raise 'no discard message found'
|
||||
end
|
||||
end
|
||||
|
||||
task_type(
|
||||
browser: instance,
|
||||
type: 'stayOnTab',
|
||||
type: params[:task_type] || 'stayOnTab',
|
||||
)
|
||||
|
||||
if params[:do_not_submit]
|
||||
@@ -2220,6 +2221,13 @@ wait untill text in selector disabppears
|
||||
|
||||
instance.find_elements(css: '.content.active .js-submit')[0].click
|
||||
|
||||
# do not stay on tab
|
||||
if params[:task_type] == 'closeTab' || params[:task_type] == 'closeNextInOverview'
|
||||
sleep 1
|
||||
screenshot(browser: instance, comment: 'ticket_update')
|
||||
return
|
||||
end
|
||||
|
||||
9.times {
|
||||
begin
|
||||
text = instance.find_elements(css: '.content.active .js-reset')[0].text
|
||||
@@ -2316,6 +2324,13 @@ wait untill text in selector disabppears
|
||||
link: "#ticket/view/#{name}",
|
||||
)
|
||||
|
||||
ticket_open_by_overview(
|
||||
browser: browser2,
|
||||
number: ticket1[:number],
|
||||
text: title,
|
||||
link: "#ticket/view/#{name}",
|
||||
)
|
||||
|
||||
=end
|
||||
|
||||
def ticket_open_by_overview(params)
|
||||
@@ -2338,12 +2353,25 @@ wait untill text in selector disabppears
|
||||
js: '$(".content.active .sidebar").css("display", "none")',
|
||||
)
|
||||
screenshot(browser: instance, comment: 'ticket_open_by_overview_search')
|
||||
instance.find_elements(partial_link_text: params[:number])[0].click
|
||||
if params[:title]
|
||||
element = instance.find_elements(partial_link_text: params[:title])[0]
|
||||
if !element
|
||||
screenshot(browser: instance, comment: 'ticket_open_by_overview_no_ticket_failed')
|
||||
raise "unable to find ticket #{params[:title]} in overview #{params[:link]}!"
|
||||
end
|
||||
else
|
||||
element = instance.find_elements(partial_link_text: params[:number])[0]
|
||||
if !element
|
||||
screenshot(browser: instance, comment: 'ticket_open_by_overview_no_ticket_failed')
|
||||
raise "unable to find ticket #{params[:number]} in overview #{params[:link]}!"
|
||||
end
|
||||
end
|
||||
element.click
|
||||
sleep 1
|
||||
number = instance.find_elements(css: '.content.active .ticketZoom-header .ticket-number')[0].text
|
||||
if number !~ /#{params[:number]}/
|
||||
screenshot(browser: instance, comment: 'ticket_open_by_overview_failed')
|
||||
raise "unable to search/find ticket #{params[:number]}!"
|
||||
screenshot(browser: instance, comment: 'ticket_open_by_overview_open_failed_failed')
|
||||
raise "unable to open ticket #{params[:number]}!"
|
||||
end
|
||||
sleep 1
|
||||
assert(true, "ticket #{params[:number]} found")
|
||||
|
||||
@@ -104,6 +104,50 @@ no reference "
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references2)
|
||||
assert_equal(ticket.id, ticket_p.id)
|
||||
|
||||
Setting.set('postmaster_follow_up_search_in', nil)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_subject)
|
||||
assert_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_body)
|
||||
assert_not_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_attachment)
|
||||
assert_not_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references1)
|
||||
assert_not_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references2)
|
||||
assert_not_equal(ticket.id, ticket_p.id)
|
||||
|
||||
Setting.set('postmaster_follow_up_search_in', 'references')
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_subject)
|
||||
assert_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_body)
|
||||
assert_not_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_attachment)
|
||||
assert_not_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references1)
|
||||
assert_equal(ticket.id, ticket_p.id)
|
||||
|
||||
travel 1.second
|
||||
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string_references2)
|
||||
assert_equal(ticket.id, ticket_p.id)
|
||||
|
||||
Setting.set('postmaster_follow_up_search_in', setting_orig)
|
||||
|
||||
travel 1.second
|
||||
@@ -325,4 +369,41 @@ Some Text"
|
||||
Setting.set('postmaster_follow_up_search_in', setting_orig)
|
||||
end
|
||||
|
||||
test 'process with follow up check - with none ticket# in subject' do
|
||||
|
||||
setting_orig = Setting.get('postmaster_follow_up_search_in')
|
||||
Setting.set('postmaster_follow_up_search_in', [])
|
||||
ticket_hook_position_orig = Setting.get('ticket_hook_position')
|
||||
Setting.set('ticket_hook_position', 'none')
|
||||
|
||||
subject = 'some title'
|
||||
email_raw_string = "From: me@example.com
|
||||
To: bob@example.com
|
||||
Subject: #{subject}
|
||||
Message-ID: <123456789-follow-up-test-ticket_hook_position-none@linuxhotel.de>
|
||||
|
||||
Some Text"
|
||||
|
||||
ticket_p1, article_1, user_1, mail = Channel::EmailParser.new.process({}, email_raw_string)
|
||||
ticket1 = Ticket.find(ticket_p1.id)
|
||||
assert_equal(subject, ticket1.title)
|
||||
|
||||
# follow up possible because same subject
|
||||
subject = 'new subject lalala'
|
||||
email_raw_string = "From: bob@example.com
|
||||
To: me@example.com
|
||||
Subject: AW: #{subject}
|
||||
Message-ID: <123456789-follow-up-test-ticket_hook_position-none-2@linuxhotel.de>
|
||||
References: <123456789-follow-up-test-ticket_hook_position-none@linuxhotel.de>
|
||||
|
||||
Some Text"
|
||||
|
||||
ticket_p2, article_p2, user_p2, mail = Channel::EmailParser.new.process({}, email_raw_string)
|
||||
ticket2 = Ticket.find(ticket_p2.id)
|
||||
assert_equal(ticket1.id, ticket2.id)
|
||||
|
||||
Setting.set('ticket_hook_position', ticket_hook_position_orig)
|
||||
Setting.set('postmaster_follow_up_search_in', setting_orig)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
File diff suppressed because one or more lines are too long
74
test/unit/html_sanitizer_test.rb
Normal file
74
test/unit/html_sanitizer_test.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
# encoding: utf-8
|
||||
require 'test_helper'
|
||||
|
||||
class HtmlSanitizerTest < ActiveSupport::TestCase
|
||||
|
||||
test 'xss' do
|
||||
assert_equal(HtmlSanitizer.strict('<b>123</b>'), '<b>123</b>')
|
||||
assert_equal(HtmlSanitizer.strict('<script><b>123</b></script>'), '<b>123</b>')
|
||||
assert_equal(HtmlSanitizer.strict('<script><style><b>123</b></style></script>'), '<style><b>123</b></style>')
|
||||
assert_equal(HtmlSanitizer.strict('<abc><i><b>123</b><bbb>123</bbb></i></abc>'), '<i><b>123</b>123</i>')
|
||||
assert_equal(HtmlSanitizer.strict('<abc><i><b>123</b><bbb>123<i><ccc>abc</ccc></i></bbb></i></abc>'), '<i><b>123</b>123<i>abc</i></i>')
|
||||
assert_equal(HtmlSanitizer.strict('<not_existing>123</not_existing>'), '123')
|
||||
assert_equal(HtmlSanitizer.strict('<script type="text/javascript">alert("XSS!");</script>'), 'alert("XSS!");')
|
||||
assert_equal(HtmlSanitizer.strict('<SCRIPT SRC=http://xss.rocks/xss.js></SCRIPT>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="javascript:alert(\'XSS\');">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC=javascript:alert(\'XSS\')>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC=JaVaScRiPt:alert(\'XSS\')>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG """><SCRIPT>alert("XSS")</SCRIPT>">'), '<img>alert("XSS")">')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC=# onmouseover="alert(\'xxs\')">'), '<img src="#">')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="jav ascript:alert(\'XSS\');">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="jav	ascript:alert(\'XSS\');">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="jav
ascript:alert(\'XSS\');">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="jav
ascript:alert(\'XSS\');">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="  javascript:alert(\'XSS\');">'), '<img src="">')
|
||||
assert_equal(HtmlSanitizer.strict('<SCRIPT/XSS SRC="http://xss.rocks/xss.js"></SCRIPT>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<SCRIPT/SRC="http://xss.rocks/xss.js"></SCRIPT>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<<SCRIPT>alert("XSS");//<</SCRIPT>'), '<alert("XSS");//<')
|
||||
assert_equal(HtmlSanitizer.strict('<SCRIPT SRC=http://xss.rocks/xss.js?< B >'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<SCRIPT SRC=//xss.rocks/.j>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="javascript:alert(\'XSS\')"'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="javascript:alert(\'XSS\')" abc<b>123</b>'), '123')
|
||||
assert_equal(HtmlSanitizer.strict('<iframe src=http://xss.rocks/scriptlet.html <'), '')
|
||||
assert_equal(HtmlSanitizer.strict('</script><script>alert(\'XSS\');</script>'), 'alert(\'XSS\');')
|
||||
assert_equal(HtmlSanitizer.strict('<STYLE>li {list-style-image: url("javascript:alert(\'XSS\')");}</STYLE><UL><LI>XSS</br>'), '<ul><li>XSS</li></ul>')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC=\'vbscript:msgbox("XSS")\'>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="livescript:[code]">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<svg/onload=alert(\'XSS\')>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<BODY ONLOAD=alert(\'XSS\')>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<LINK REL="stylesheet" HREF="javascript:alert(\'XSS\');">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<STYLE>@import\'http://xss.rocks/xss.css\';</STYLE>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<META HTTP-EQUIV="Link" Content="<http://xss.rocks/xss.css>; REL=stylesheet">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG STYLE="java/*XSS*/script:(alert(\'XSS\'), \'\')">'), '<img>')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG src="java/*XSS*/script:(alert(\'XSS\'), \'\')">'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<IFRAME SRC="javascript:alert(\'XSS\');"></IFRAME>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<TABLE><TD BACKGROUND="javascript:alert(\'XSS\')">'), '<table><td></td></table>')
|
||||
assert_equal(HtmlSanitizer.strict('<DIV STYLE="background-image: url(javascript:alert(\'XSS\'), \'\')">'), '<div></div>')
|
||||
assert_equal(HtmlSanitizer.strict('<a href="/some/path">test</a>'), '<a href="/some/path">test</a>')
|
||||
assert_equal(HtmlSanitizer.strict('<a href="https://some/path">test</a>'), '<a href="https://some/path" rel="nofollow" target="_blank">test</a>')
|
||||
assert_equal(HtmlSanitizer.strict('<XML ID="xss"><I><B><IMG SRC="javas<!-- -->cript:alert(\'XSS\')"></B></I></XML>'), '<i><b></b></i>')
|
||||
assert_equal(HtmlSanitizer.strict('<IMG SRC="javas<!-- -->cript:alert(\'XSS\')">'), '')
|
||||
assert_equal(HtmlSanitizer.strict(' <HEAD><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD>+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-'), ' +ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-')
|
||||
assert_equal(HtmlSanitizer.strict('<SCRIPT a=">" SRC="httx://xss.rocks/xss.js"></SCRIPT>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<A HREF="h
|
||||
tt p://6 6.000146.0x7.147/">XSS</A>'), '<a href="h%0Att%20%20p://6%206.000146.0x7.147/" rel="nofollow" target="_blank">XSS</a>')
|
||||
assert_equal(HtmlSanitizer.strict('<A HREF="//www.google.com/">XSS</A>'), '<a href="//www.google.com/" rel="nofollow" target="_blank">XSS</a>')
|
||||
assert_equal(HtmlSanitizer.strict('<form id="test"></form><button form="test" formaction="javascript:alert(1)">X</button>'), 'X')
|
||||
assert_equal(HtmlSanitizer.strict('<maction actiontype="statusline#http://google.com" xlink:href="javascript:alert(2)">CLICKME</maction>'), 'CLICKME')
|
||||
assert_equal(HtmlSanitizer.strict('<a xlink:href="javascript:alert(2)">CLICKME</a>'), '<a>CLICKME</a>')
|
||||
assert_equal(HtmlSanitizer.strict('<!--<img src="--><img src=x onerror=alert(1)//">'), '<img src="x">')
|
||||
assert_equal(HtmlSanitizer.strict('<![><img src="]><img src=x onerror=alert(1)//">'), '<img src="%5D><img%20src=x%20onerror=alert(1)//">')
|
||||
assert_equal(HtmlSanitizer.strict('<svg><![CDATA[><image xlink:href="]]><img src=xx:x onerror=alert(2)//"></svg>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<abc><img src="</abc><img src=x onerror=alert(1)//">'), '<img src="</abc><img%20src=x%20onerror=alert(1)//">')
|
||||
assert_equal(HtmlSanitizer.strict('<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></embed>'), '')
|
||||
assert_equal(HtmlSanitizer.strict('<img[a][b]src=x[d]onerror[c]=[e]"alert(1)">'), '<img>')
|
||||
assert_equal(HtmlSanitizer.strict('<a href="[a]java[b]script[c]:alert(1)">XXX</a>'), '<a>XXX</a>')
|
||||
assert_equal(HtmlSanitizer.strict('<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>'), 'alert(1)')
|
||||
assert_equal(HtmlSanitizer.strict('<a style="position:fixed;top:0;left:0;width: 260px;height:100vh;background-color:red;display: block;" href="http://example.com"></a>'), '<a style="width: 260px;height:100vh;" href="http://example.com" rel="nofollow" target="_blank"></a>')
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
185
test/unit/ticket_xss_test.rb
Normal file
185
test/unit/ticket_xss_test.rb
Normal file
@@ -0,0 +1,185 @@
|
||||
# encoding: utf-8
|
||||
require 'test_helper'
|
||||
|
||||
class TicketXssTest < ActiveSupport::TestCase
|
||||
test 'xss via model' do
|
||||
ticket = Ticket.create(
|
||||
title: 'test 123 <script type="text/javascript">alert("XSS!");</script>',
|
||||
group: Group.lookup(name: 'Users'),
|
||||
customer_id: 2,
|
||||
state: Ticket::State.lookup(name: 'new'),
|
||||
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert(ticket, 'ticket created')
|
||||
|
||||
assert_equal('test 123 <script type="text/javascript">alert("XSS!");</script>', ticket.title, 'ticket.title verify')
|
||||
assert_equal('Users', ticket.group.name, 'ticket.group verify')
|
||||
assert_equal('new', ticket.state.name, 'ticket.state verify')
|
||||
|
||||
article1 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/html',
|
||||
body: '<script type="text/javascript">alert("XSS!");</script>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal('alert("XSS!");', article1.body, 'article1.body verify - inbound')
|
||||
|
||||
article2 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/html',
|
||||
body: 'please tell me this doesn\'t work: <script type="text/javascript">alert("XSS!");</script>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal('please tell me this doesn\'t work: alert("XSS!");', article2.body, 'article2.body verify - inbound')
|
||||
|
||||
article3 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/html',
|
||||
body: 'please tell me this doesn\'t work: <table>ada<tr></tr></table><div class="adasd" id="123" data-abc="123"></div><div><a href="javascript:someFunction()">LINK</a><a href="http://lalal.de">aa</a><some_not_existing>ABC</some_not_existing>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal("please tell me this doesn't work: <table>ada<tr></tr>
|
||||
</table><div class=\"adasd\"></div><div>
|
||||
<a>LINK</a><a href=\"http://lalal.de\" rel=\"nofollow\" target=\"_blank\">aa</a>ABC</div>", article3.body, 'article3.body verify - inbound')
|
||||
|
||||
article4 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/html',
|
||||
body: 'please tell me this doesn\'t work: <video>some video</video><foo>alal</foo>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal("please tell me this doesn't work: <video>some video</video>alal", article4.body, 'article4.body verify - inbound')
|
||||
|
||||
article5 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/plain',
|
||||
body: 'please tell me this doesn\'t work: <table>ada<tr></tr></table><div class="adasd" id="123" data-signature-id="123"></div><div><a href="javascript:someFunction()">LINK</a><a href="http://lalal.de">aa</a><some_not_existing>ABC</some_not_existing>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal('please tell me this doesn\'t work: <table>ada<tr></tr></table><div class="adasd" id="123" data-signature-id="123"></div><div><a href="javascript:someFunction()">LINK</a><a href="http://lalal.de">aa</a><some_not_existing>ABC</some_not_existing>', article5.body, 'article5.body verify - inbound')
|
||||
|
||||
article6 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/html',
|
||||
body: 'some message article helper test1 <div><img style="width: 85.5px; height: 49.5px" src="cid:15.274327094.140938@zammad.example.com">asdasd<img src="cid:15.274327094.140939@zammad.example.com"><br>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal('some message article helper test1 <div>
|
||||
<img style="width: 85.5px; height: 49.5px;" src="cid:15.274327094.140938@zammad.example.com">asdasd<img src="cid:15.274327094.140939@zammad.example.com"><br>
|
||||
</div>', article6.body, 'article6.body verify - inbound')
|
||||
|
||||
article7 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/html',
|
||||
body: 'some message article helper test1 <div><img style="width: 85.5px; height: 49.5px" src="api/v1/ticket_attachment/123/123/123">asdasd<img src="api/v1/ticket_attachment/123/123/123"><br>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal('some message article helper test1 <div>
|
||||
<img style="width: 85.5px; height: 49.5px;" src="api/v1/ticket_attachment/123/123/123">asdasd<img src="api/v1/ticket_attachment/123/123/123"><br>
|
||||
</div>', article7.body, 'article7.body verify - inbound')
|
||||
|
||||
article8 = Ticket::Article.create(
|
||||
ticket_id: ticket.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject <script type="text/javascript">alert("XSS!");</script>',
|
||||
message_id: 'some@id',
|
||||
content_type: 'text/html',
|
||||
body: 'some message article helper test1 <a href="#" onclick="some_function();">abc</a> <a href="https://example.com" oNclIck="some_function();">123</a><body>123</body>',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert_equal('some message article helper test1 <a href="#">abc</a> <a href="https://example.com" rel="nofollow" target="_blank">123</a>123', article8.body, 'article8.body verify - inbound')
|
||||
|
||||
end
|
||||
|
||||
test 'xss via mail' do
|
||||
data = 'From: ME Bob <me@example.com>
|
||||
To: customer@example.com
|
||||
Subject: some subject
|
||||
Content-Type: text/html
|
||||
MIME-Version: 1.0
|
||||
|
||||
no HTML <script type="text/javascript">alert(\'XSS\')</script>'
|
||||
|
||||
parser = Channel::EmailParser.new
|
||||
ticket, article, user = parser.process({}, data)
|
||||
assert_equal('text/html', ticket.articles.first.content_type)
|
||||
assert_equal('no HTML alert(\'XSS\')', ticket.articles.first.body)
|
||||
|
||||
data = 'From: ME Bob <me@example.com>
|
||||
To: customer@example.com
|
||||
Subject: some subject
|
||||
Content-Type: text/plain
|
||||
MIME-Version: 1.0
|
||||
|
||||
no HTML <script type="text/javascript">alert(\'XSS\')</script>'
|
||||
|
||||
parser = Channel::EmailParser.new
|
||||
ticket, article, user = parser.process({}, data)
|
||||
assert_equal('text/plain', ticket.articles.first.content_type)
|
||||
assert_equal('no HTML <script type="text/javascript">alert(\'XSS\')</script>', ticket.articles.first.body)
|
||||
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user