Browse Source

Fixes #1961 - S/MIME integration.

Thorsten Eckel 4 years ago
parent
commit
2ffca390a9

+ 5 - 2
Gemfile

@@ -38,6 +38,9 @@ gem 'aasm'
 # core - authorization
 gem 'pundit'
 
+# core - image processing
+gem 'rszr', '0.5.2'
+
 # performance - Memcached
 gem 'dalli'
 
@@ -130,8 +133,8 @@ gem 'autodiscover', git: 'https://github.com/zammad-deps/autodiscover'
 gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm'
 gem 'viewpoint'
 
-# image processing
-gem 'rszr', '0.5.2'
+# integrations - S/MIME
+gem 'openssl'
 
 # Gems used only for develop/test and not required
 # in production environments by default.

+ 2 - 0
Gemfile.lock

@@ -355,6 +355,7 @@ GEM
     omniauth-weibo-oauth2 (0.5.2)
       omniauth (~> 1.5)
       omniauth-oauth2 (>= 1.4.0)
+    openssl (2.1.2)
     parallel (1.19.1)
     parser (2.7.1.2)
       ast (~> 2.4.0)
@@ -629,6 +630,7 @@ DEPENDENCIES
   omniauth-saml
   omniauth-twitter
   omniauth-weibo-oauth2
+  openssl
   pg (= 0.21.0)
   pre-commit
   pry-rails

+ 283 - 283
LICENSE-ICONS-3RD-PARTY.json

@@ -1,230 +1,245 @@
 {
-    "spinner-small.svg": {
-        "author": "Felix Niklas",
+    "archived-modifier.svg": {
+        "author": "",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "sms.svg": {
+    "arrow-down.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "unordered-list.svg": {
+    "arrow-left.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "underline.svg": {
+    "arrow-right.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "strikethrough.svg": {
+    "arrow-up.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "split.svg": {
+    "bold.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "reply.svg": {
+    "chain.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "weibo-button.svg": {
-        "author": "Weibo",
+    "chat.svg": {
+        "author": "Felix Niklas",
         "url": "",
-        "license": ""
+        "license": "MIT"
     },
-    "telegram.svg": {
-        "author": "Telegram",
+    "checkbox-checked.svg": {
+        "author": "Zammad",
         "url": "",
-        "license": ""
-    },
-    "twitter-button.svg": {
-        "author": "Twitter",
-        "url": "twitter.com",
-        "license": ""
+        "license": "MIT"
     },
-    "zoom-out.svg": {
+    "checkbox-indeterminate.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "zoom-in.svg": {
-        "author": "Felix Niklas",
+    "checkbox.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "web.svg": {
+    "checkmark.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "user.svg": {
-        "author": "R\u00e9my M\u00e9dard",
-        "url": "https:\/\/thenounproject.com\/search\/?q=user&i=10314",
-        "license": "CC 3.0 Attribution"
-    },
-    "unmute.svg": {
+    "clipboard.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "twitter.svg": {
-        "author": "Twitter",
-        "url": "twitter.com",
-        "license": ""
-    },
-    "trash.svg": {
-        "author": "Filip Malinowski",
-        "url": "https:\/\/thenounproject.com\/term\/trash\/16505\/",
-        "license": "CC 3.0 Attribution"
-    },
-    "total-tickets.svg": {
-        "author": "Zammad",
+    "clock.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "tools.svg": {
-        "author": "Michael Kussmaul",
-        "url": "https:\/\/thenounproject.com\/term\/tools\/41655\/",
+    "cloud.svg": {
+        "author": "Kirill Ulitin",
+        "url": "https:\/\/thenounproject.com\/search\/?q=cloud&i=84976",
         "license": "CC 3.0 Attribution"
     },
-    "templates.svg": {
+    "cog.svg": {
+        "author": "Melvin Salas",
+        "url": "https:\/\/thenounproject.com\/term\/gear\/17369\/",
+        "license": "CC 3.0 Attribution"
+    },
+    "crown.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "team.svg": {
-        "author": "R\u00e9my M\u00e9dard",
-        "url": "https:\/\/thenounproject.com\/catalarem\/uploads\/?i=2554",
+    "danger.svg": {
+        "author": "",
+        "url": "",
+        "license": ""
+    },
+    "dashboard.svg": {
+        "author": "Anton Gajdosik",
+        "url": "https:\/\/thenounproject.com\/term\/gauge\/186120",
         "license": "CC 3.0 Attribution"
     },
-    "task-state.svg": {
+    "diagonal-cross.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "switchView.svg": {
-        "author": "Zammad",
+    "document.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "stopwatch.svg": {
-        "author": "Zammad",
+    "download.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "status.svg": {
-        "author": "Zammad",
+    "draft-modifier.svg": {
+        "author": "",
+        "url": "",
+        "license": ""
+    },
+    "draggable.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "status-modified-outer-circle.svg": {
+    "dropdown-list.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "file-text.svg": {
+    "email-button.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "radio.svg": {
-        "author": "Zammad",
+    "email.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "radio-checked.svg": {
-        "author": "Zammad",
+    "external.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "clock.svg": {
+    "eye.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "important.svg": {
+    "eyedropper.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "diagonal-cross.svg": {
-        "author": "Zammad",
+    "facebook-button.svg": {
+        "author": "Facebook",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "lock-open.svg": {
-        "author": "Zammad",
+    "facebook.svg": {
+        "author": "Facebook",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "rearange.svg": {
+    "file-archive.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "external.svg": {
+    "file-code.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "mood-sad.svg": {
+    "file-email.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "radio.svg": {
-        "author": "Zammad",
+    "file-excel.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "radio-checked.svg": {
-        "author": "Zammad",
+    "file-pdf.svg": {
+        "author": "Adobe",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "knowledge-base.svg": {
+    "file-powerpoint.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "eye.svg": {
+    "file-text.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "document.svg": {
+    "file-unknown.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "low-priority.svg": {
+    "file-word.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "long-arrow-down.svg": {
+    "form.svg": {
+        "author": "Pickin Studio",
+        "url": "https:\/\/thenounproject.com\/search\/?q=website&i=16523",
+        "license": "CC 3.0 Attribution"
+    },
+    "forward.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "italic.svg": {
-        "author": "Felix Niklas",
+    "full-logo.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "inactive-user.svg": {
+    "github-button.svg": {
+        "author": "Github",
+        "url": "",
+        "license": ""
+    },
+    "gitlab-button.svg": {
+        "author": "Gitlab",
+        "url": "",
+        "license": ""
+    },
+    "google-button.svg": {
+        "author": "Google",
+        "url": "",
+        "license": ""
+    },
+    "group.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "inactive-organization.svg": {
+    "help.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
@@ -239,452 +254,437 @@
         "url": "",
         "license": "MIT"
     },
-    "reply-all.svg": {
-        "author": "Felix Niklas",
+    "in-process.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "paperclip.svg": {
-        "author": "Cheesefork",
-        "url": "https:\/\/thenounproject.com\/search\/?q=attachment&i=197956",
-        "license": "CC 3.0 Attribution"
-    },
-    "overflow-button.svg": {
+    "inactive-organization.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "lock.svg": {
-        "author": "Zammad",
+    "inactive-user.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "lock-open.svg": {
-        "author": "Zammad",
+    "info.svg": {
+        "author": "Information by Gregor \u010cre\u0161nar from the Noun Project",
+        "url": "https:\/\/thenounproject.com\/search\/?q=info&i=176431",
+        "license": "CC 3.0 Attribution"
+    },
+    "internal-modifier.svg": {
+        "author": "",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "forward.svg": {
+    "italic.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-word.svg": {
+    "knowledge-base-answer.svg": {
+        "author": "",
+        "url": "",
+        "license": ""
+    },
+    "knowledge-base.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-unknown.svg": {
+    "line-left-arrow.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-powerpoint.svg": {
+    "line-right-arrow.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-pdf.svg": {
-        "author": "Adobe",
+    "linkedin-button.svg": {
+        "author": "Linkedin",
         "url": "",
         "license": ""
     },
-    "file-excel.svg": {
-        "author": "Felix Niklas",
+    "list.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "file-email.svg": {
-        "author": "Felix Niklas",
+    "loading.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "file-code.svg": {
-        "author": "Felix Niklas",
+    "lock-open.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "file-archive.svg": {
-        "author": "Felix Niklas",
+    "lock.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "office365-button.svg": {
-        "author": "Office 365",
+    "logo.svg": {
+        "author": "Zammad",
         "url": "",
-        "license": ""
+        "license": "MIT"
     },
-    "logo.svg": {
+    "logotype.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "printer.svg": {
+    "long-arrow-down.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "note.svg": {
+    "long-arrow-right.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
+    },
+    "low-priority.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "phone.svg": {
-        "author": "Michael Zenaty",
-        "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797",
-        "license": "CC 3.0 Attribution"
+    "magnifier.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
     },
-    "email.svg": {
+    "marker.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "oauth2-button.svg": {
+    "message.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "linkedin-button.svg": {
-        "author": "Linkedin",
+    "minus-small.svg": {
+        "author": "Zammad",
         "url": "",
-        "license": ""
+        "license": "MIT"
     },
-    "google-button.svg": {
-        "author": "Google",
+    "minus.svg": {
+        "author": "Zammad",
         "url": "",
-        "license": ""
+        "license": "MIT"
     },
-    "gitlab-button.svg": {
-        "author": "Gitlab",
+    "mood-bad.svg": {
+        "author": "Zammad",
         "url": "",
-        "license": ""
+        "license": "MIT"
     },
-    "github-button.svg": {
-        "author": "Github",
+    "mood-good.svg": {
+        "author": "Zammad",
         "url": "",
-        "license": ""
+        "license": "MIT"
     },
-    "facebook-button.svg": {
-        "author": "Facebook",
+    "mood-ok.svg": {
+        "author": "Zammad",
         "url": "",
-        "license": ""
+        "license": "MIT"
     },
-    "email-button.svg": {
+    "mood-sad.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "line-right-arrow.svg": {
-        "author": "Felix Niklas",
+    "mood-superbad.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "small-dot.svg": {
+    "mood-supergood.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "signout.svg": {
+    "mute.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "searchdetail.svg": {
+    "not-signed.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
+    },
+    "note.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "report.svg": {
-        "author": "Zammad",
+    "oauth2-button.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "reopening.svg": {
+    "office365-button.svg": {
+        "author": "Office 365",
+        "url": "",
+        "license": ""
+    },
+    "one-ticket.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "reload.svg": {
-        "author": "Anand A Nair",
-        "url": "https:\/\/thenounproject.com\/anandgrafiti\/uploads\/?i=2149",
-        "license": "CC 3.0 Attribution"
+    "organization.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
     },
-    "received-calls.svg": {
+    "outbound-calls.svg": {
         "author": "Michael Zenaty",
         "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797",
         "license": "CC 3.0 Attribution"
     },
-    "plus.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
-    },
-    "plus-small.svg": {
-        "author": "Zammad",
+    "overflow-button.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "person.svg": {
+    "overviews.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "pen.svg": {
-        "author": "Dmitry Baranovskiy",
-        "url": "https:\/\/thenounproject.com\/search\/?q=edit&i=5039",
-        "license": "CC 3.0 Attribution"
-    },
     "package.svg": {
         "author": "Michael Wallner",
         "url": "https:\/\/thenounproject.com\/search\/?q=package&i=25152",
         "license": "CC 3.0 Attribution"
     },
-    "overviews.svg": {
+    "paperclip.svg": {
+        "author": "Cheesefork",
+        "url": "https:\/\/thenounproject.com\/search\/?q=attachment&i=197956",
+        "license": "CC 3.0 Attribution"
+    },
+    "pen.svg": {
+        "author": "Dmitry Baranovskiy",
+        "url": "https:\/\/thenounproject.com\/search\/?q=edit&i=5039",
+        "license": "CC 3.0 Attribution"
+    },
+    "person.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "outbound-calls.svg": {
+    "phone.svg": {
         "author": "Michael Zenaty",
         "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797",
         "license": "CC 3.0 Attribution"
     },
-    "organization.svg": {
-        "author": "Felix Niklas",
+    "plus-small.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "one-ticket.svg": {
+    "plus.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "mute.svg": {
+    "printer.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "mood-supergood.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
-    },
-    "mood-superbad.svg": {
+    "radio-checked.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "mood-ok.svg": {
+    "radio.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "mood-good.svg": {
-        "author": "Zammad",
+    "rearange.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "mood-bad.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
+    "received-calls.svg": {
+        "author": "Michael Zenaty",
+        "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797",
+        "license": "CC 3.0 Attribution"
     },
-    "minus.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
+    "reload.svg": {
+        "author": "Anand A Nair",
+        "url": "https:\/\/thenounproject.com\/anandgrafiti\/uploads\/?i=2149",
+        "license": "CC 3.0 Attribution"
     },
-    "minus-small.svg": {
+    "reopening.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "message.svg": {
+    "reply-all.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "marker.svg": {
+    "reply.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "magnifier.svg": {
+    "report.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "long-arrow-right.svg": {
-        "author": "Zammad",
+    "searchdetail.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "logotype.svg": {
+    "signed.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "loading.svg": {
-        "author": "Zammad",
+    "signout.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "list.svg": {
+    "small-dot.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "line-left-arrow.svg": {
+    "sms.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "info.svg": {
-        "author": "Information by Gregor \u010cre\u0161nar from the Noun Project",
-        "url": "https:\/\/thenounproject.com\/search\/?q=info&i=176431",
-        "license": "CC 3.0 Attribution"
-    },
-    "in-process.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
-    },
-    "help.svg": {
+    "spinner-small.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "group.svg": {
+    "split.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "full-logo.svg": {
+    "status-modified-outer-circle.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "form.svg": {
-        "author": "Pickin Studio",
-        "url": "https:\/\/thenounproject.com\/search\/?q=website&i=16523",
-        "license": "CC 3.0 Attribution"
-    },
-    "facebook.svg": {
-        "author": "Facebook",
-        "url": "",
-        "license": ""
-    },
-    "eyedropper.svg": {
-        "author": "Felix Niklas",
+    "status.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "dropdown-list.svg": {
+    "stopwatch.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "draggable.svg": {
+    "strikethrough.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "download.svg": {
-        "author": "Felix Niklas",
+    "switchView.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "dashboard.svg": {
-        "author": "Anton Gajdosik",
-        "url": "https:\/\/thenounproject.com\/term\/gauge\/186120",
-        "license": "CC 3.0 Attribution"
-    },
-    "crown.svg": {
-        "author": "Felix Niklas",
+    "task-state.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "cog.svg": {
-        "author": "Melvin Salas",
-        "url": "https:\/\/thenounproject.com\/term\/gear\/17369\/",
-        "license": "CC 3.0 Attribution"
-    },
-    "cloud.svg": {
-        "author": "Kirill Ulitin",
-        "url": "https:\/\/thenounproject.com\/search\/?q=cloud&i=84976",
+    "team.svg": {
+        "author": "R\u00e9my M\u00e9dard",
+        "url": "https:\/\/thenounproject.com\/catalarem\/uploads\/?i=2554",
         "license": "CC 3.0 Attribution"
     },
-    "clipboard.svg": {
-        "author": "Felix Niklas",
+    "telegram.svg": {
+        "author": "Telegram",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "bold.svg": {
+    "templates.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "checkbox.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
-    },
-    "checkbox-indeterminate.svg": {
-        "author": "Felix Niklas",
-        "url": "",
-        "license": "MIT"
+    "tools.svg": {
+        "author": "Michael Kussmaul",
+        "url": "https:\/\/thenounproject.com\/term\/tools\/41655\/",
+        "license": "CC 3.0 Attribution"
     },
-    "checkmark.svg": {
+    "total-tickets.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "chain.svg": {
-        "author": "Felix Niklas",
-        "url": "",
-        "license": "MIT"
+    "trash.svg": {
+        "author": "Filip Malinowski",
+        "url": "https:\/\/thenounproject.com\/term\/trash\/16505\/",
+        "license": "CC 3.0 Attribution"
     },
-    "bold.svg": {
-        "author": "Felix Niklas",
-        "url": "",
-        "license": "MIT"
+    "twitter-button.svg": {
+        "author": "Twitter",
+        "url": "twitter.com",
+        "license": ""
     },
-    "checkbox.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
+    "twitter.svg": {
+        "author": "Twitter",
+        "url": "twitter.com",
+        "license": ""
     },
-    "checkbox-indeterminate.svg": {
+    "underline.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "checkbox-checked.svg": {
-        "author": "Zammad",
+    "unmute.svg": {
+        "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "arrow-down.svg": {
+    "unordered-list.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "arrow-up.svg": {
-        "author": "Felix Niklas",
+    "user.svg": {
+        "author": "R\u00e9my M\u00e9dard",
+        "url": "https:\/\/thenounproject.com\/search\/?q=user&i=10314",
+        "license": "CC 3.0 Attribution"
+    },
+    "web.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "arrow-right.svg": {
-        "author": "Felix Niklas",
+    "weibo-button.svg": {
+        "author": "Weibo",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "arrow-left.svg": {
+    "zoom-in.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "chat.svg": {
+    "zoom-out.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"

+ 258 - 0
app/assets/javascripts/app/controllers/_integration/smime.coffee

@@ -0,0 +1,258 @@
+class Index extends App.ControllerIntegrationBase
+  featureIntegration: 'smime_integration'
+  featureName: 'S/MIME'
+  featureConfig: 'smime_config'
+  description: [
+    ['S/MIME (Secure/Multipurpose Internet Mail Extensions) 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: 'S/MIME'
+    )
+
+class Form extends App.Controller
+  events:
+    'click .js-addCertificate': 'addCertificate'
+    'click .js-addPrivateKey': 'addPrivateKey'
+    'click .js-updateGroup': 'updateGroup'
+
+  constructor: ->
+    super
+    @render()
+
+  currentConfig: ->
+    App.Setting.get('smime_config')
+
+  setConfig: (value) ->
+    App.Setting.set('smime_config', value, {notify: true})
+
+  render: =>
+    @config = @currentConfig()
+
+    @html App.view('integration/smime')(
+      config: @config
+    )
+    @certList()
+    @groupList()
+
+  certList: =>
+    new List(el: @$('.js-certList'))
+
+  groupList: =>
+    new Group(
+      el: @$('.js-groupList')
+      config: @config
+    )
+
+  addCertificate: =>
+    new Certificate(
+      callback: @list
+    )
+
+  addPrivateKey: =>
+    new PrivateKey(
+      callback: @list
+    )
+
+  updateGroup: (e) =>
+    params = App.ControllerForm.params(e)
+    @setConfig(params)
+
+class Certificate extends App.ControllerModal
+  buttonClose: true
+  buttonCancel: true
+  buttonSubmit: 'Add'
+  autoFocusOnFirstInput: false
+  head: 'Add Certificate'
+  large: true
+
+  content: ->
+
+    # show start dialog
+    content = $(App.view('integration/smime_certificate_add')(
+      head: 'Add Certificate'
+    ))
+    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:          'smime-certificate-add'
+      type:        'POST'
+      url:         "#{@apiPath}/integration/smime/certificate"
+      processData: false
+      contentType: false
+      cache:       false
+      data:        params
+      success:     (data, status, xhr) =>
+        console.log('success')
+        @close()
+        @callback()
+      error: (data) =>
+        console.log('error')
+        @close()
+        details = data.responseJSON || {}
+        @notify
+          type:    'error'
+          msg:     App.i18n.translateContent(details.error_human || details.error || 'Unable to import!')
+          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/smime_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:          'smime-private_key-add'
+      type:        'POST'
+      url:         "#{@apiPath}/integration/smime/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 || 'Unable to import!')
+          timeout: 6000
+    )
+
+
+class List extends App.Controller
+  events:
+    'click .js-remove': 'remove'
+
+  constructor: ->
+    super
+    @load()
+
+  load: =>
+    @ajax(
+      id:    'smime-list'
+      type:  'GET'
+      url:   "#{@apiPath}/integration/smime/certificate"
+      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 || 'Unable to load list of certificates!')
+        )
+
+        # do something
+    )
+
+  render: (data) =>
+    @html App.view('integration/smime_list')(
+      certs: data
+    )
+
+  remove: (e) =>
+    e.preventDefault()
+    id = $(e.currentTarget).parents('tr').data('id')
+    return if !id
+
+    @ajax(
+      id:    'smime-list'
+      type:  'DELETE'
+      url:   "#{@apiPath}/integration/smime/certificate"
+      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 || 'Unable to save!')
+        )
+    )
+
+class Group extends App.Controller
+  constructor: ->
+    super
+    @render()
+
+  render: (data) =>
+    groups = App.Group.search(sortBy: 'name', filter: active: true)
+    @html App.view('integration/smime_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('smime_integration')
+
+App.Config.set(
+  'Integrationsmime'
+  {
+    name: 'S/MIME'
+    target: '#system/integration/smime'
+    description: 'S/MIME enables you to send digitally signed and encrypted messages.'
+    controller: Index
+    state: State
+  }
+  'NavBarIntegrations'
+)

+ 29 - 0
app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee

@@ -428,6 +428,35 @@ class App.UiElement.ticket_perform_action
 
     elementRow.find('.js-setNotification').html(notificationElement).removeClass('hide')
 
+    if App.Config.get('smime_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)

+ 36 - 5
app/assets/javascripts/app/controllers/agent_ticket_create.coffee

@@ -1,11 +1,14 @@
 class App.TicketCreate extends App.Controller
+  @include App.SecurityOptions
+
   elements:
     '.tabsSidebar': 'sidebar'
 
   events:
-    'click .type-tabs .tab': 'changeFormType'
-    'submit form':           'submit'
-    'click .js-cancel':      'cancel'
+    'click .type-tabs .tab':   'changeFormType'
+    'submit form':             'submit'
+    'click .js-cancel':        'cancel'
+    'click .js-active-toggle': 'toggleButton'
 
   types: {
     'phone-in': {
@@ -121,12 +124,25 @@ class App.TicketCreate extends App.Controller
     # force changing signature
     @$('[name="group_id"]').trigger('change')
 
+    # add observer to change options
+    @$('[name="cc"], [name="group_id"], [name="customer_id"]').bind('change', =>
+      @updateSecurityOptions()
+    )
+    @updateSecurityOptions()
+
     # show cc
     if type is 'email-out'
       @$('[name="cc"]').closest('.form-group').removeClass('hide')
+
+      if @securityEnabled()
+        @securityOptionsShow()
+
     else
       @$('[name="cc"]').closest('.form-group').addClass('hide')
 
+      if @securityEnabled()
+        @securityOptionsHide()
+
     # show notice
     @$('.js-note').addClass('hide')
     @$(".js-note[data-type='#{type}']").removeClass('hide')
@@ -165,6 +181,13 @@ class App.TicketCreate extends App.Controller
     return false if !diff || _.isEmpty(diff)
     return true
 
+  updateSecurityOptions: =>
+    params = @params()
+    if params.customer_id_completion
+      params.to = params.customer_id_completion
+
+    @updateSecurityOptionsRemote(@taskKey, params, params, @paramsSecurity())
+
   autosaveStop: =>
     @clearDelay('ticket-create-form-update')
     @el.off('change.local blur.local keyup.local paste.local input.local')
@@ -385,6 +408,9 @@ class App.TicketCreate extends App.Controller
 
     @tokanice()
 
+  toggleButton: (event) ->
+    @$(event.currentTarget).toggleClass('btn--active')
+
   tokanice: ->
     App.Utils.tokanice('.content.active input[name=cc]', 'email')
 
@@ -447,7 +473,7 @@ class App.TicketCreate extends App.Controller
 
     # create article
     if sender.name is 'Customer'
-      params['article'] = {
+      params.article = {
         to:           (group && group.name) || ''
         from:         params.customer_id_completion
         cc:           params.cc
@@ -459,7 +485,7 @@ class App.TicketCreate extends App.Controller
         content_type: 'text/html'
       }
     else
-      params['article'] = {
+      params.article = {
         from:         (group && group.name) || ''
         to:           params.customer_id_completion
         cc:           params.cc
@@ -471,6 +497,11 @@ class App.TicketCreate extends App.Controller
         content_type: 'text/html'
       }
 
+      # add security params
+      if @securityOptionsShown()
+        params.article.preferences ||= {}
+        params.article.preferences.security = @paramsSecurity()
+
     ticket.load(params)
 
     ticketErrorsTop = ticket.validate(

+ 6 - 4
app/assets/javascripts/app/controllers/ticket_zoom.coffee

@@ -709,12 +709,11 @@ class App.TicketZoom extends App.Controller
   markFormDiff: (diff = {}) =>
     ticketForm    = @$('.edit')
     ticketSidebar = @$('.tabsSidebar-tab[data-tab="ticket"]')
-    articleForm   = @$('.article-add')
     resetButton   = @$('.js-reset')
 
     params         = {}
-    params.ticket  = @forRemoveMeta(@formParam(ticketForm))
-    params.article = @forRemoveMeta(@formParam(articleForm))
+    params.ticket  = @forRemoveMeta(@ticketParams())
+    params.article = @forRemoveMeta(@articleNew.params())
 
     # clear all changes
     if _.isEmpty(diff.ticket) && _.isEmpty(diff.article)
@@ -743,6 +742,9 @@ class App.TicketZoom extends App.Controller
 
       resetButton.removeClass('hide')
 
+  ticketParams: =>
+    @formParam(@$('.edit'))
+
   submitDisable: (e) =>
     if e
       @formDisable(e)
@@ -767,7 +769,7 @@ class App.TicketZoom extends App.Controller
       @submitEnable(e)
       return
 
-    ticketParams = @formParam(@$('.edit'))
+    ticketParams = @ticketParams()
     articleParams = @articleNew.params()
 
     # validate ticket

+ 1 - 1
app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee

@@ -253,7 +253,7 @@ class EmailReply extends App.Controller
       icon:       'email'
       attributes: attributes
       internal:   false,
-      features:   ['attachment']
+      features:   ['attachment', 'security']
     }
 
     articleTypes

+ 29 - 0
app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee

@@ -1,4 +1,6 @@
 class App.TicketZoomArticleNew extends App.Controller
+  @include App.SecurityOptions
+
   elements:
     '.js-textarea':                       'textarea'
     '.attachmentPlaceholder':             'attachmentPlaceholder'
@@ -24,6 +26,7 @@ class App.TicketZoomArticleNew extends App.Controller
     'click .list-entry-type div':    'changeType'
     'focus .js-textarea':            'openTextarea'
     'input .js-textarea':            'updateLetterCount'
+    'click .js-active-toggle':       'toggleButton'
 
   constructor: ->
     super
@@ -106,6 +109,12 @@ class App.TicketZoomArticleNew extends App.Controller
       @render()
     )
 
+    # update security options
+    @bind('ui::ticket::updateSecurityOptions', (data) =>
+      return if data.taskKey isnt @taskKey
+      @updateSecurityOptions()
+    )
+
   tokanice: (type = 'email') ->
     App.Utils.tokanice('.content.active .js-to, .js-cc, js-bcc', type)
 
@@ -294,6 +303,11 @@ class App.TicketZoomArticleNew extends App.Controller
             params.body = "#{params.body}\n#{@signature.text()}"
           break
 
+    # add security params
+    if @securityOptionsShown()
+      params.preferences ||= {}
+      params.preferences.security = @paramsSecurity()
+
     params
 
   validate: =>
@@ -417,6 +431,15 @@ class App.TicketZoomArticleNew extends App.Controller
             @warningTextLength = articleType.warningTextLength
             @delay(@updateLetterCount, 600)
             @$('.js-textSizeLimit').removeClass('hide')
+          if name is 'security'
+            if @securityEnabled()
+              @securityOptionsShow()
+
+              # add observer to change options
+              @$('.js-to, .js-cc').bind('change', =>
+                @updateSecurityOptions()
+              )
+              @updateSecurityOptions()
 
     # convert remote src images to data uri
     @$('[data-name=body] img').each( (i,image) ->
@@ -434,6 +457,9 @@ class App.TicketZoomArticleNew extends App.Controller
 
     @scrollToBottom() if wasScrolledToBottom
 
+  updateSecurityOptions: =>
+    @updateSecurityOptionsRemote(@ticket.id, @ui.ticketParams(), @params(), @paramsSecurity())
+
   setArticleTypePost: (type, signaturePosition = 'bottom') =>
     for localConfig in @actions()
       if localConfig && localConfig.setArticleTypePost
@@ -625,3 +651,6 @@ class App.TicketZoomArticleNew extends App.Controller
       if localConfig
         actions.push localConfig
     actions
+
+  toggleButton: (event) ->
+    @$(event.currentTarget).toggleClass('btn--active')

+ 47 - 5
app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee

@@ -81,11 +81,13 @@ class ArticleViewItem extends App.ObserverController
     '.textBubble-overflowContainer': 'textBubbleOverflowContainer'
 
   events:
-    'click .textBubble':           'toggleMetaWithDelay'
-    'click .textBubble a':         'stopPropagation'
-    'click .js-toggleFold':        'toggleFold'
-    'click .richtext-content img': 'imageView'
-    'click .attachments img':      'imageView'
+    'click .article-meta-permanent':  'toggleMetaWithDelay'
+    'click .textBubble':              'toggleMetaWithDelay'
+    'click .textBubble a':            'stopPropagation'
+    'click .js-toggleFold':           'toggleFold'
+    'click .richtext-content img':    'imageView'
+    'click .attachments img':         'imageView'
+    'click .js-securityRetryProcess': 'retrySecurityProcess'
 
   constructor: ->
     super
@@ -296,6 +298,46 @@ class ArticleViewItem extends App.ObserverController
     else
       bubbleOverflowContainer.addClass('hide')
 
+  retrySecurityProcess: (e) ->
+    e.preventDefault()
+    e.stopPropagation()
+
+    article_id = $(e.target).closest('.ticket-article-item').data('id')
+
+    @ajax(
+      id:   'retrySecurityProcess'
+      type: 'POST'
+      url:  "#{@apiPath}/ticket_articles/#{article_id}/retry_security_process"
+      processData: true
+      success: (data, status, xhr) =>
+        if data.sign.success
+          @notify
+            type: 'success'
+            msg:  App.i18n.translateContent('Verify sign success!')
+        else if data.sign.comment
+          comment = App.i18n.translateContent('Verify sign failed!') + ' ' + App.i18n.translateContent(data.sign.comment || '')
+          @notify
+            type: 'error'
+            msg: comment
+            timeout: 2000
+
+        if data.encryption.success
+          @notify
+            type: 'success'
+            msg:  App.i18n.translateContent('Decryption success!')
+        else if data.encryption.comment
+          comment = App.i18n.translateContent('Decryption failed!') + ' ' + App.i18n.translateContent(data.encryption.comment || '')
+          @notify
+            type: 'error'
+            msg:  comment
+            timeout: 2000
+
+      error: (xhr) =>
+        @notify
+          type: 'error'
+          msg:  App.i18n.translateContent('Retry security process failed!')
+    )
+
   stopPropagation: (e) ->
     e.stopPropagation()
 

Some files were not shown because too many files changed in this diff