summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--service/pixelated/adapter/model/mail.py38
-rw-r--r--service/pixelated/bitmask_libraries/certs.py34
-rw-r--r--service/pixelated/bitmask_libraries/config.py3
-rw-r--r--service/pixelated/bitmask_libraries/register.py3
-rw-r--r--service/pixelated/bitmask_libraries/session.py4
-rw-r--r--service/pixelated/config/leap_cert.py2
-rw-r--r--service/pixelated/support/ext_requests_urllib3.py107
-rw-r--r--service/pixelated/support/tls_adapter.py2
-rw-r--r--service/test/unit/adapter/test_mail.py34
-rw-r--r--service/test/unit/bitmask_libraries/test_certs.py6
-rw-r--r--web-ui/app/index.html44
-rw-r--r--web-ui/app/locales/en-us/translation.json7
-rw-r--r--web-ui/app/locales/en/translation.json7
-rw-r--r--web-ui/app/locales/sv/translation.json2
-rw-r--r--web-ui/app/scss/_security.scss6
-rw-r--r--web-ui/app/scss/styles.scss2
-rw-r--r--web-ui/app/templates/mails/single.hbs41
18 files changed, 210 insertions, 140 deletions
diff --git a/README.md b/README.md
index dfc1531f..4af4dc9b 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,12 @@ From the project root folder, set up the vagrant machine:
You can log into the machine and view project root folder with:
vagrant ssh
+
+ Then you need to run the setup:
+
+ cd service
+ ./go setup
+
From here on you can run the tests for the UI by going to the **web-ui** folder or for the API by going to the **service** folder:
cd /vagrant/web-ui
@@ -42,7 +48,7 @@ From here on you can run the tests for the UI by going to the **web-ui** folder
Running the user agent:
```
-$ pixelated-user-agent --host 0.0.0.0
+$ pixelated-user-agent --host 0.0.0.0 -lc /vagrant/service/pixelated/certificates/dev.pixelated-project.org.ca.crt
> 2015-01-23 11:18:07+0100 [-] Log opened.
> 2015-01-23 11:18:07+0100 [-] Which provider do you want to connect to:
dev.pixelated-project.org
diff --git a/service/pixelated/adapter/model/mail.py b/service/pixelated/adapter/model/mail.py
index f23c2708..618b980a 100644
--- a/service/pixelated/adapter/model/mail.py
+++ b/service/pixelated/adapter/model/mail.py
@@ -21,6 +21,7 @@ from email.header import decode_header
from leap.mail.imap.fields import fields
import leap.mail.walk as walk
import dateutil.parser as dateparser
+from datetime import datetime
from pixelated.adapter.model.status import Status
import pixelated.support.date
from email.MIMEMultipart import MIMEMultipart
@@ -291,7 +292,10 @@ class PixelatedMail(Mail):
for header in ['From', 'Subject']:
_headers[header] = self._decode_header(hdoc_headers.get(header))
- _headers['Date'] = self._get_date()
+ try:
+ _headers['Date'] = self._get_date()
+ except Exception, e:
+ _headers['Date'] = pixelated.support.date.iso_now()
if self.parts and len(self.parts['alternatives']) > 1:
_headers['content_type'] = 'multipart/alternative; boundary="%s"' % self.boundary
@@ -303,25 +307,35 @@ class PixelatedMail(Mail):
return _headers
+ def _decode_header_with_fallback(self, entry):
+ try:
+ return decode_header(entry)[0][0]
+ except Exception, e:
+ return entry.encode('ascii', 'ignore')
+
def _decode_header(self, header):
if not header:
return None
if isinstance(header, list):
- return [decode_header(entry)[0][0] for entry in header]
+ return [self._decode_header_with_fallback(entry) for entry in header]
else:
- return decode_header(header)[0][0]
+ return self._decode_header_with_fallback(header)
def _get_date(self):
date = self.hdoc.content.get('date', None)
- if not date:
- received = self.hdoc.content.get('received', None)
- if received:
- date = received.split(";")[-1].strip()
- else:
- # we can't get a date for this mail, so lets just use now
- logger.warning('Encountered a mail with missing date and received header fields. Subject %s' % self.hdoc.content.get('subject', None))
- date = pixelated.support.date.iso_now()
- return dateparser.parse(date).isoformat()
+ try:
+ if not date:
+ received = self.hdoc.content.get('received', None)
+ if received:
+ date = received.split(";")[-1].strip()
+ else:
+ # we can't get a date for this mail, so lets just use now
+ logger.warning('Encountered a mail with missing date and received header fields. Subject %s' % self.hdoc.content.get('subject', None))
+ date = pixelated.support.date.iso_now()
+ return dateparser.parse(date).isoformat()
+ except (ValueError, TypeError) as e:
+ date = pixelated.support.date.iso_now()
+ return dateparser.parse(date).isoformat()
@property
def security_casing(self):
diff --git a/service/pixelated/bitmask_libraries/certs.py b/service/pixelated/bitmask_libraries/certs.py
index 31e68d1c..a321e00e 100644
--- a/service/pixelated/bitmask_libraries/certs.py
+++ b/service/pixelated/bitmask_libraries/certs.py
@@ -14,13 +14,15 @@
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import os
-
+import requests
+import json
from leap.common import ca_bundle
from .config import AUTO_DETECT_CA_BUNDLE
LEAP_CERT = None
LEAP_FINGERPRINT = None
+PACKAGED_CERTS_HOME = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "certificates"))
def which_api_CA_bundle(provider):
@@ -45,7 +47,6 @@ class LeapCertificate(object):
def __init__(self, provider):
self._config = provider.config
self._server_name = provider.server_name
- self._certs_home = self._config.certs_home
self._provider = provider
def auto_detect_bootstrap_ca_bundle(self):
@@ -84,12 +85,35 @@ class LeapCertificate(object):
return path
def _local_bootstrap_server_cert(self):
- cert_file = os.path.join(self._certs_home, '%s.ca.crt' % self._server_name)
- if not os.path.isfile(cert_file):
- self._download_server_cert(cert_file)
+ cert_file = self._bootstrap_certs_cert_file()
+ if os.path.isfile(cert_file):
+ return cert_file
+
+ cert_file = os.path.join(PACKAGED_CERTS_HOME, '%s.ca.crt' % self._server_name)
+ if os.path.exists(cert_file):
+ return cert_file
+
+ # else download the file
+ cert_file = self._bootstrap_certs_cert_file()
+ response = requests.get('https://%s/provider.json' % self._server_name)
+ provider_data = json.loads(response.content)
+ ca_cert_uri = str(provider_data['ca_cert_uri'])
+
+ response = requests.get(ca_cert_uri)
+ with open(cert_file, 'w') as file:
+ file.write(response.content)
return cert_file
+ def _bootstrap_certs_cert_file(self):
+ path = os.path.join(self._provider.config.leap_home, 'providers', self._server_name)
+ if not os.path.isdir(path):
+ os.makedirs(path, 0700)
+
+ file_path = os.path.join(path, '%s.ca.crt' % self._server_name)
+
+ return file_path
+
def _download_server_cert(self, cert_file_name):
cert = self._provider.fetch_valid_certificate()
diff --git a/service/pixelated/bitmask_libraries/config.py b/service/pixelated/bitmask_libraries/config.py
index 56f28706..8c862d0a 100644
--- a/service/pixelated/bitmask_libraries/config.py
+++ b/service/pixelated/bitmask_libraries/config.py
@@ -45,7 +45,7 @@ class LeapConfig(object):
def __init__(self, leap_home=DEFAULT_LEAP_HOME, bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE,
ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, verify_ssl=True,
fetch_interval_in_s=30,
- timeout_in_s=15, start_background_jobs=False, gpg_binary=discover_gpg_binary(), certs_home=None):
+ timeout_in_s=15, start_background_jobs=False, gpg_binary=discover_gpg_binary()):
"""
Constructor.
@@ -75,7 +75,6 @@ class LeapConfig(object):
"""
self.leap_home = leap_home
- self.certs_home = certs_home
self.bootstrap_ca_cert_bundle = bootstrap_ca_cert_bundle
self.ca_cert_bundle = ca_cert_bundle
self.verify_ssl = verify_ssl
diff --git a/service/pixelated/bitmask_libraries/register.py b/service/pixelated/bitmask_libraries/register.py
index 258394da..bb54477f 100644
--- a/service/pixelated/bitmask_libraries/register.py
+++ b/service/pixelated/bitmask_libraries/register.py
@@ -25,8 +25,7 @@ from pixelated.bitmask_libraries.auth import LeapAuthenticator, LeapCredentials
def register_new_user(username, server_name):
- certs_home = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "certificates"))
- config = LeapConfig(certs_home=certs_home)
+ config = LeapConfig()
provider = LeapProvider(server_name, config)
password = getpass.getpass('Please enter password for %s: ' % username)
LeapAuthenticator(provider).register(LeapCredentials(username, password))
diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py
index c0c8f712..eb6f39d7 100644
--- a/service/pixelated/bitmask_libraries/session.py
+++ b/service/pixelated/bitmask_libraries/session.py
@@ -37,9 +37,7 @@ SESSIONS = {}
def open(username, password, server_name, leap_home=DEFAULT_LEAP_HOME):
- certs_home = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "certificates"))
-
- config = LeapConfig(leap_home=leap_home, certs_home=certs_home)
+ config = LeapConfig(leap_home=leap_home)
provider = LeapProvider(server_name, config)
refresh_ca_bundle(provider)
session = LeapSessionFactory(provider).create(LeapCredentials(username, password))
diff --git a/service/pixelated/config/leap_cert.py b/service/pixelated/config/leap_cert.py
index 3172c953..22f73720 100644
--- a/service/pixelated/config/leap_cert.py
+++ b/service/pixelated/config/leap_cert.py
@@ -19,7 +19,7 @@ import pixelated.bitmask_libraries.certs as certs
def init_leap_cert(args):
if args.leap_cert_fingerprint is None:
- certs.LEAP_CERT = args.leap_cert
+ certs.LEAP_CERT = args.leap_cert or True
certs.LEAP_FINGERPRINT = None
else:
certs.LEAP_FINGERPRINT = args.leap_cert_fingerprint
diff --git a/service/pixelated/support/ext_requests_urllib3.py b/service/pixelated/support/ext_requests_urllib3.py
index a836d6fd..c4ec2438 100644
--- a/service/pixelated/support/ext_requests_urllib3.py
+++ b/service/pixelated/support/ext_requests_urllib3.py
@@ -15,65 +15,68 @@
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import requests
-import requests.packages.urllib3.connectionpool
-from socket import error as SocketError, timeout as SocketTimeout
-from requests.packages.urllib3.packages.ssl_match_hostname import CertificateError, match_hostname
-import socket
-import ssl
-from requests.packages.urllib3.exceptions import (
- ClosedPoolError,
- ConnectTimeoutError,
- EmptyPoolError,
- HostChangedError,
- MaxRetryError,
- SSLError,
- ReadTimeoutError,
- ProxyError,
-)
-from requests.packages.urllib3.util import (
- assert_fingerprint,
- get_host,
- is_connection_dropped,
- resolve_cert_reqs,
- resolve_ssl_version,
- ssl_wrap_socket,
- Timeout,
-)
+if requests.__version__ == '2.0.0':
+ try:
+ import requests.packages.urllib3.connectionpool
+ from socket import error as SocketError, timeout as SocketTimeout
+ from requests.packages.urllib3.packages.ssl_match_hostname import CertificateError, match_hostname
+ import socket
+ import ssl
+ from requests.packages.urllib3.exceptions import (
+ ClosedPoolError,
+ ConnectTimeoutError,
+ EmptyPoolError,
+ HostChangedError,
+ MaxRetryError,
+ SSLError,
+ ReadTimeoutError,
+ ProxyError,
+ )
-def patched_connect(self):
- # Add certificate verification
- try:
- sock = socket.create_connection(address=(self.host, self.port), timeout=self.timeout)
- except SocketTimeout:
- raise ConnectTimeoutError(self, "Connection to %s timed out. (connect timeout=%s)" % (self.host, self.timeout))
+ from requests.packages.urllib3.util import (
+ assert_fingerprint,
+ get_host,
+ is_connection_dropped,
+ resolve_cert_reqs,
+ resolve_ssl_version,
+ ssl_wrap_socket,
+ Timeout,
+ )
- resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs)
- resolved_ssl_version = resolve_ssl_version(self.ssl_version)
+ def patched_connect(self):
+ # Add certificate verification
+ try:
+ sock = socket.create_connection(address=(self.host, self.port), timeout=self.timeout)
+ except SocketTimeout:
+ raise ConnectTimeoutError(self, "Connection to %s timed out. (connect timeout=%s)" % (self.host, self.timeout))
- if self._tunnel_host:
- self.sock = sock
- # Calls self._set_hostport(), so self.host is
- # self._tunnel_host below.
- self._tunnel()
+ resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs)
+ resolved_ssl_version = resolve_ssl_version(self.ssl_version)
- # Wrap socket using verification with the root certs in
- # trusted_root_certs
- self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
- cert_reqs=resolved_cert_reqs,
- ca_certs=self.ca_certs,
- server_hostname=self.host,
- ssl_version=resolved_ssl_version)
+ if self._tunnel_host:
+ self.sock = sock
+ # Calls self._set_hostport(), so self.host is
+ # self._tunnel_host below.
+ self._tunnel()
- if self.assert_fingerprint:
- assert_fingerprint(self.sock.getpeercert(binary_form=True),
- self.assert_fingerprint)
- elif resolved_cert_reqs != ssl.CERT_NONE and self.assert_hostname is not False:
- match_hostname(self.sock.getpeercert(),
- self.assert_hostname or self.host)
+ # Wrap socket using verification with the root certs in
+ # trusted_root_certs
+ self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
+ cert_reqs=resolved_cert_reqs,
+ ca_certs=self.ca_certs,
+ server_hostname=self.host,
+ ssl_version=resolved_ssl_version)
+ if self.assert_fingerprint:
+ assert_fingerprint(self.sock.getpeercert(binary_form=True),
+ self.assert_fingerprint)
+ elif resolved_cert_reqs != ssl.CERT_NONE and self.assert_hostname is not False:
+ match_hostname(self.sock.getpeercert(),
+ self.assert_hostname or self.host)
-if requests.__version__ == '2.0.0':
- requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection.connect = patched_connect
+ requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection.connect = patched_connect
+ except ImportError:
+ pass # The patch is specific for the debian package. Ignore it if it can't be found
diff --git a/service/pixelated/support/tls_adapter.py b/service/pixelated/support/tls_adapter.py
index f543bf4d..301a2123 100644
--- a/service/pixelated/support/tls_adapter.py
+++ b/service/pixelated/support/tls_adapter.py
@@ -41,7 +41,7 @@ class EnforceTLSv1Adapter(HTTPAdapter):
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
- block=block, ssl_version=latest_available_ssl_version(),
+ block=block,
assert_hostname=self._assert_hostname,
assert_fingerprint=self._assert_fingerprint,
cert_reqs=ssl.CERT_REQUIRED)
diff --git a/service/test/unit/adapter/test_mail.py b/service/test/unit/adapter/test_mail.py
index c7910b7f..d77816cd 100644
--- a/service/test/unit/adapter/test_mail.py
+++ b/service/test/unit/adapter/test_mail.py
@@ -1,3 +1,4 @@
+# -*- coding: UTF-8 -*-
#
# Copyright (c) 2014 ThoughtWorks, Inc.
#
@@ -66,6 +67,28 @@ class TestPixelatedMail(unittest.TestCase):
self.assertEqual(str(mail.headers['Date']), leap_mail_date_in_iso_format)
+ def test_use_datetime_now_as_fallback_for_invalid_date(self):
+ leap_mail_date = u'söme däte'
+ date_expected = "2014-09-03T13:11:15-03:00"
+
+ when(pixelated.support.date).iso_now().thenReturn(date_expected)
+ leap_mail = test_helper.leap_mail(headers={'date': leap_mail_date})
+
+ mail = PixelatedMail.from_soledad(*leap_mail, soledad_querier=self.querier)
+
+ self.assertEqual(str(mail.headers['Date']), date_expected)
+
+ def test_fall_back_to_ascii_if_invalid_received_header(self):
+ leap_mail_received_header = u"söme invalid received heäder\n"
+ date_expected = "2014-09-03T13:11:15-03:00"
+
+ when(pixelated.support.date).iso_now().thenReturn(date_expected)
+ leap_mail = test_helper.leap_mail(headers={'received': leap_mail_received_header})
+
+ mail = PixelatedMail.from_soledad(*leap_mail, soledad_querier=self.querier)
+
+ self.assertEqual(mail.headers['Date'], date_expected)
+
def test_update_tags_return_a_set_with_the_current_tags(self):
soledad_docs = test_helper.leap_mail(extra_headers={'X-tags': '["custom_1", "custom_2"]'})
pixelated_mail = PixelatedMail.from_soledad(*soledad_docs, soledad_querier=self.querier)
@@ -334,6 +357,17 @@ class TestPixelatedMail(unittest.TestCase):
mail.as_dict()
+ def test_parse_UTF8_headers_with_CharsetAscii(self):
+ leap_mail_from = u'"söme ümläuds" <lisa5@dev.pixelated-project.org>'
+ leap_mail_to = u'"söme ümläuds" <lisa5@dev.pixelated-project.org>,\n"söme ümläuds" <lisa5@dev.pixelated-project.org>'
+
+ leap_mail = test_helper.leap_mail(extra_headers={'From': leap_mail_from, 'Subject': "some subject", 'To': leap_mail_to})
+
+ mail = PixelatedMail.from_soledad(*leap_mail, soledad_querier=self.querier)
+
+ mail.headers['From'].encode('ascii')
+ self.assertEqual(mail.headers['To'], ['"sme mluds" <lisa5@dev.pixelated-project.org>', '"sme mluds" <lisa5@dev.pixelated-project.org>'])
+
def simple_mail_dict():
return {
diff --git a/service/test/unit/bitmask_libraries/test_certs.py b/service/test/unit/bitmask_libraries/test_certs.py
index ba56d5c2..4a06649d 100644
--- a/service/test/unit/bitmask_libraries/test_certs.py
+++ b/service/test/unit/bitmask_libraries/test_certs.py
@@ -12,12 +12,12 @@ class CertsTest(unittest.TestCase):
def test_that_which_bootstrap_cert_bundle_returns_byte_string(self, mock_isdir, mock_isfile):
mock_isfile.return_value = True
mock_isdir.return_value = True
- config = MagicMock(bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, certs_home='/some/path')
+ config = MagicMock(bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, leap_home='/leap/home')
provider = MagicMock(server_name=u'test.leap.net', config=config)
bundle = which_bootstrap_CA_bundle(provider)
- self.assertEqual('/some/path/test.leap.net.ca.crt', bundle)
+ self.assertEqual('/leap/home/providers/test.leap.net/test.leap.net.ca.crt', bundle)
self.assertEqual(str, type(bundle))
@patch('pixelated.bitmask_libraries.certs.os.path.isfile')
@@ -26,7 +26,7 @@ class CertsTest(unittest.TestCase):
mock_isfile.return_value = True
mock_isdir.return_value = True
- config = MagicMock(bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, ca_cert_bundle=None, leap_home='/some/leap/home', certs_home='/some/path')
+ config = MagicMock(bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, ca_cert_bundle=None, leap_home='/some/leap/home')
provider = MagicMock(server_name=u'test.leap.net', config=config)
bundle = which_api_CA_bundle(provider)
diff --git a/web-ui/app/index.html b/web-ui/app/index.html
index 503cdde6..d6e03de0 100644
--- a/web-ui/app/index.html
+++ b/web-ui/app/index.html
@@ -21,28 +21,28 @@
<div class="inner-wrap">
<section id="left-pane" class="left-off-canvas-menu">
<a class="left-off-canvas-logo" href="#">
- <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="30.4 316.8 555.2 155.2" enable-background="new 30.4 316.8 555.2 155.2" xml:space="preserve">
- <g>
- <path fill="#3E3B38" d="M30.4,355.5v77.8L98.9,472l68.5-38.7v-77.8l-68.5-38.7L30.4,355.5z M96,443.9l-39.9-22v-47.7L96,396.9
- V443.9z M141.2,421.8l-39.4,22v-47l39.4-23C141.2,373.8,141.2,421.8,141.2,421.8z M141.2,367.5l-42.4,25.2l-42.8-25.2l42.8-23
- L141.2,367.5z"/>
- <path fill="#3E3B38" d="M214.9,363.1h-21.8v64.6h14.7v-24h7.1c12.7,0,22-7.3,22-20.8C237,369.7,227.4,363.1,214.9,363.1z M212,392
- h-4.2v-17.1h4.2c5.9,0,11.3,2,11.3,8.6S217.9,392,212,392z"/>
- <rect x="241.9" y="363.1" fill="#3E3B38" width="14.7" height="64.6"/>
- <polygon fill="#3E3B38" points="320.7,363.1 302.3,363.1 290.3,380.7 278.3,363.1 261,363.1 281.3,392.9 259.2,427.7 277.6,427.7
- 290.3,405.7 303.1,427.7 322.2,427.7 299.4,392.9 "/>
- <polygon fill="#3E3B38" points="324.6,427.7 361.6,427.7 361.6,414.7 339.3,414.7 339.3,401.8 360.6,401.8 360.6,388.8
- 339.3,388.8 339.3,376 361.6,376 361.6,363.1 324.6,363.1 "/>
- <path fill="#3E3B38" d="M416.6,363.1l-20.8,51.7h-14.4v-51.7h-14.7v64.6h24h13h2.9l4.9-13H436l4.9,13h15.9l-26.2-64.6H416.6z
- M416.2,401.8l7.1-18.8h0.2l7.1,18.8H416.2z"/>
- <polygon fill="#3E3B38" points="444.1,376 459.5,376 459.5,427.7 474.2,427.7 474.2,376 489.6,376 489.6,363.1 444.1,363.1 "/>
- <polygon fill="#3E3B38" points="494.5,427.7 531.5,427.7 531.5,414.7 509.4,414.7 509.4,401.8 530.7,401.8 530.7,388.8
- 509.4,388.8 509.4,376 531.5,376 531.5,363.1 494.5,363.1 "/>
- <path fill="#3E3b38" d="M553,363.1h-16.2v64.6H553c17.9,0,32.6-13.5,32.6-32.3C585.6,376.5,570.6,363.1,553,363.1z M553.5,414.5
- h-2.2v-38.2h2.2c11,0,18.4,8.3,18.4,19.1C571.9,406.2,564.5,414.5,553.5,414.5z"/>
- </g>
- </svg>
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="30.4 316.8 555.2 155.2" enable-background="new 30.4 316.8 555.2 155.2" xml:space="preserve">
+ <g>
+ <path fill="#3E3B38" d="M30.4,355.5v77.8L98.9,472l68.5-38.7v-77.8l-68.5-38.7L30.4,355.5z M96,443.9l-39.9-22v-47.7L96,396.9
+ V443.9z M141.2,421.8l-39.4,22v-47l39.4-23C141.2,373.8,141.2,421.8,141.2,421.8z M141.2,367.5l-42.4,25.2l-42.8-25.2l42.8-23
+ L141.2,367.5z"/>
+ <path fill="#3E3B38" d="M214.9,363.1h-21.8v64.6h14.7v-24h7.1c12.7,0,22-7.3,22-20.8C237,369.7,227.4,363.1,214.9,363.1z M212,392
+ h-4.2v-17.1h4.2c5.9,0,11.3,2,11.3,8.6S217.9,392,212,392z"/>
+ <rect x="241.9" y="363.1" fill="#3E3B38" width="14.7" height="64.6"/>
+ <polygon fill="#3E3B38" points="320.7,363.1 302.3,363.1 290.3,380.7 278.3,363.1 261,363.1 281.3,392.9 259.2,427.7 277.6,427.7
+ 290.3,405.7 303.1,427.7 322.2,427.7 299.4,392.9 "/>
+ <polygon fill="#3E3B38" points="324.6,427.7 361.6,427.7 361.6,414.7 339.3,414.7 339.3,401.8 360.6,401.8 360.6,388.8
+ 339.3,388.8 339.3,376 361.6,376 361.6,363.1 324.6,363.1 "/>
+ <path fill="#3E3B38" d="M416.6,363.1l-20.8,51.7h-14.4v-51.7h-14.7v64.6h24h13h2.9l4.9-13H436l4.9,13h15.9l-26.2-64.6H416.6z
+ M416.2,401.8l7.1-18.8h0.2l7.1,18.8H416.2z"/>
+ <polygon fill="#3E3B38" points="444.1,376 459.5,376 459.5,427.7 474.2,427.7 474.2,376 489.6,376 489.6,363.1 444.1,363.1 "/>
+ <polygon fill="#3E3B38" points="494.5,427.7 531.5,427.7 531.5,414.7 509.4,414.7 509.4,401.8 530.7,401.8 530.7,388.8
+ 509.4,388.8 509.4,376 531.5,376 531.5,363.1 494.5,363.1 "/>
+ <path fill="#3E3b38" d="M553,363.1h-16.2v64.6H553c17.9,0,32.6-13.5,32.6-32.3C585.6,376.5,570.6,363.1,553,363.1z M553.5,414.5
+ h-2.2v-38.2h2.2c11,0,18.4,8.3,18.4,19.1C571.9,406.2,564.5,414.5,553.5,414.5z"/>
+ </g>
+ </svg>
</a>
<a class="side-nav-toggle side-nav-toggle-icon" href="#">
<i class="toggle fa fa-navicon"></i>
diff --git a/web-ui/app/locales/en-us/translation.json b/web-ui/app/locales/en-us/translation.json
index 8cdb419e..2a474c80 100644
--- a/web-ui/app/locales/en-us/translation.json
+++ b/web-ui/app/locales/en-us/translation.json
@@ -43,10 +43,9 @@
"no_recipient": "<No Recipients>",
"you": "you",
"encrypted": "Encrypted",
- "encrypted encryption-failure": "You are not authorized to see this message.",
- "encrypted encryption-error": "Message was encrypted but we couldn't decrypt it.",
- "encrypted encryption-valid": "Message was transmitted securely.",
- "not-encrypted": "Message was readable during transmission.",
+ "encrypted encryption-error": "Unable to decrypt",
+ "encrypted encryption-valid": "Encrypted",
+ "not-encrypted": "Not Encrypted",
"signed": "Certified sender.",
"signed signature-revoked": "Sender could not be securely identified.",
"signed signature-expired": "Sender could not be securely identified.",
diff --git a/web-ui/app/locales/en/translation.json b/web-ui/app/locales/en/translation.json
index 8cdb419e..2a474c80 100644
--- a/web-ui/app/locales/en/translation.json
+++ b/web-ui/app/locales/en/translation.json
@@ -43,10 +43,9 @@
"no_recipient": "<No Recipients>",
"you": "you",
"encrypted": "Encrypted",
- "encrypted encryption-failure": "You are not authorized to see this message.",
- "encrypted encryption-error": "Message was encrypted but we couldn't decrypt it.",
- "encrypted encryption-valid": "Message was transmitted securely.",
- "not-encrypted": "Message was readable during transmission.",
+ "encrypted encryption-error": "Unable to decrypt",
+ "encrypted encryption-valid": "Encrypted",
+ "not-encrypted": "Not Encrypted",
"signed": "Certified sender.",
"signed signature-revoked": "Sender could not be securely identified.",
"signed signature-expired": "Sender could not be securely identified.",
diff --git a/web-ui/app/locales/sv/translation.json b/web-ui/app/locales/sv/translation.json
index e999ce9d..69557f6a 100644
--- a/web-ui/app/locales/sv/translation.json
+++ b/web-ui/app/locales/sv/translation.json
@@ -43,7 +43,7 @@
"no_recipient": "<Inga mottagare>",
"you": "du",
"encrypted": "krypterad",
- "encrypted encryption-failure": "Du har inte tillstånd att see det här meddelandet.",
+ "encrypted encryption-error": "Du har inte tillstånd att see det här meddelandet.",
"encrypted encryption-valid": "Meddelandet skickades säkert.",
"not-encrypted": "Meddelandet var läsbart medans det var på väg.",
"signed": "Certifierad avsändare.",
diff --git a/web-ui/app/scss/_security.scss b/web-ui/app/scss/_security.scss
index 2a6b60aa..be306d86 100644
--- a/web-ui/app/scss/_security.scss
+++ b/web-ui/app/scss/_security.scss
@@ -14,12 +14,6 @@
&:before {
content: "\f023 \f00c";
}
- &.encryption-failure {
- background: $error;
- &:before {
- content: "\f023 \f05e";
- }
- }
&.encryption-error {
background: $attention;
&:before {
diff --git a/web-ui/app/scss/styles.scss b/web-ui/app/scss/styles.scss
index 2e47dad7..6dd82c4e 100644
--- a/web-ui/app/scss/styles.scss
+++ b/web-ui/app/scss/styles.scss
@@ -429,7 +429,7 @@ section {
}
&#middle-pane {
- background: lighten($contrast, 2%);
+ background: $contrast;
@include email-list;
}
diff --git a/web-ui/app/templates/mails/single.hbs b/web-ui/app/templates/mails/single.hbs
index 997ab44f..a74c9606 100644
--- a/web-ui/app/templates/mails/single.hbs
+++ b/web-ui/app/templates/mails/single.hbs
@@ -1,25 +1,26 @@
<span>
- <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
+ <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
</span>
<span>
- <a href="/#/{{ tag }}/mail/{{ ident }}">
- <span class="received-date">{{ header.formattedDate }}
- {{#if attachments}}
- <div class="attachment-indicator">
- <i class="fa fa-paperclip"></i>
- </div>
- {{/if}}
- </span>
- <div class="from">{{#if header.from }}{{ header.from }}{{else}}{{t "you"}}{{/if}}</div>
- <div class="subject-and-tags">
- {{ header.subject }}
+ <a href="/#/{{ tag }}/mail/{{ ident }}">
+ <span class="received-date">{{ header.formattedDate }}
+ {{#if attachments}}
+ <div class="attachment-indicator">
+ <i class="fa fa-paperclip"></i>
</div>
- <div class="subject-and-tags">
- <ul class="tags">
- {{#each tagsForListView }}
- <li class="tag" data-tag="{{this}}">{{ this }}</li>
- {{/each }}
- </ul>
- </div>
- </a>
+ {{/if}}
+ </span>
+ <div class="from">{{#if header.from }}{{ header.from }}{{else}}{{t "you"}}{{/if}}</div>
+ <div class="subject-and-tags">
+ <i class="fa fa-trash-o"></i>
+ {{ header.subject }}
+ </div>
+ <div class="subject-and-tags">
+ <ul class="tags">
+ {{#each tagsForListView }}
+ <li class="tag" data-tag="{{this}}">{{ this }}</li>
+ {{/each }}
+ </ul>
+ </div>
+ </a>
</span>