summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ruby-version1
-rw-r--r--osx_setup.sh39
-rw-r--r--service/pixelated/adapter/mailstore/leap_mailstore.py1
-rw-r--r--service/pixelated/adapter/mailstore/maintenance/__init__.py49
-rw-r--r--service/pixelated/adapter/search/__init__.py3
-rw-r--r--service/pixelated/resources/keys_resource.py2
-rw-r--r--service/pixelated/resources/logout_resource.py19
-rw-r--r--service/requirements.txt14
-rw-r--r--service/test/unit/adapter/mailstore/maintenance/test_soledad_maintenance.py38
-rw-r--r--service/test/unit/adapter/search/test_search.py4
-rw-r--r--service/test/unit/resources/test_keys_resources.py24
-rw-r--r--service/test/unit/resources/test_logout_resources.py15
-rw-r--r--web-ui/app/images/logo.svg30
-rw-r--r--web-ui/app/js/mail_view/ui/mail_view.js1
-rw-r--r--web-ui/app/js/mail_view/ui/reply_section.js20
-rw-r--r--web-ui/app/js/page/events.js3
-rw-r--r--web-ui/app/scss/_mixins.scss1
-rw-r--r--web-ui/app/scss/_read.scss2
-rw-r--r--web-ui/app/scss/_security.scss3
-rw-r--r--web-ui/test/spec/mail_view/ui/mail_view.spec.js5
-rw-r--r--web-ui/test/spec/mail_view/ui/reply_section.spec.js16
21 files changed, 203 insertions, 87 deletions
diff --git a/.ruby-version b/.ruby-version
deleted file mode 100644
index 58594069..00000000
--- a/.ruby-version
+++ /dev/null
@@ -1 +0,0 @@
-2.2.3
diff --git a/osx_setup.sh b/osx_setup.sh
index a8a37833..f899b227 100644
--- a/osx_setup.sh
+++ b/osx_setup.sh
@@ -1,5 +1,35 @@
#!/bin/bash
+# Test to make sure we are OSX
+if [ $(uname) != 'Darwin' ]
+then
+ echo "This script should run only on an OSX system!"
+ exit 1
+fi
+
+# Read the shell configured for the user and set the variable file accordingly
+function current_shell {
+ case $SHELL in
+ *bash)
+ echo ~/.bash_profile
+ ;;
+
+ *zsh)
+ echo ~/.zprofile
+ ;;
+
+ /bin/sh)
+ echo ~/.profile
+ ;;
+
+ *)
+ echo "Your shell isn't supported yet!"
+ ;;
+
+ #Other shells can go here
+ esac
+}
+
function install_compass {
rbenv install -s 2.2.3
eval "$(rbenv init -)"
@@ -7,8 +37,10 @@ function install_compass {
rbenv local 2.2.3
gem install compass
export PATH=$PATH:~/.rbenv/versions/2.2.3/bin
- echo "export PATH=$PATH:~/.rbenv/versions/2.2.3/bin" >> ~/.bash_profile
- echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
+ echo "export PATH=$PATH:~/.rbenv/versions/2.2.3/bin" >> $(current_shell)
+ echo 'eval "$(rbenv init -)"' >> $(current_shell)
+ echo "export PATH=$PATH:~/.rbenv/versions/2.2.3/bin" >> $(current_shell)
+ echo 'eval "$(rbenv init -)"' >> $(current_shell)
}
function install_rbenv {
@@ -30,13 +62,14 @@ function clone_repo {
cd pixelated-user-agent
fi
}
+
#setup frontend
install_rbenv
install_compass
install_npm
#setup backend
-brew install python # force brew install even if python is already installed
+brew install python # force brew install even if python is already install
export LDFLAGS=-L/usr/local/opt/openssl/lib
export LDFLAGS=-L/usr/local/opt/openssl/lib
pip install virtualenv
diff --git a/service/pixelated/adapter/mailstore/leap_mailstore.py b/service/pixelated/adapter/mailstore/leap_mailstore.py
index 975bcc5c..cd4cb5b8 100644
--- a/service/pixelated/adapter/mailstore/leap_mailstore.py
+++ b/service/pixelated/adapter/mailstore/leap_mailstore.py
@@ -27,6 +27,7 @@ from pixelated.adapter.mailstore.mailstore import MailStore, underscore_uuid
from pixelated.adapter.model.mail import Mail, InputMail
from pixelated.support import log_time_deferred
from pixelated.support.functional import to_unicode
+from pixelated.support import date
MIME_PGP_KEY = 'application/pgp-keys'
diff --git a/service/pixelated/adapter/mailstore/maintenance/__init__.py b/service/pixelated/adapter/mailstore/maintenance/__init__.py
index edc442c2..9b6d6023 100644
--- a/service/pixelated/adapter/mailstore/maintenance/__init__.py
+++ b/service/pixelated/adapter/mailstore/maintenance/__init__.py
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
-from leap.keymanager.keys import KEY_TYPE_KEY, KEY_PRIVATE_KEY, KEY_ID_KEY, KEY_ADDRESS_KEY
+from leap.keymanager.keys import KEY_TYPE_KEY, KEY_PRIVATE_KEY, KEY_FINGERPRINT_KEY, KEY_ADDRESS_KEY
from leap.keymanager.openpgp import OpenPGPKey
from twisted.internet import defer
@@ -44,8 +44,8 @@ def _is_public_key(doc):
return _is_key_doc(doc) and not doc.content.get(KEY_PRIVATE_KEY, False)
-def _key_id(doc):
- return doc.content.get(KEY_ID_KEY, None)
+def _key_fingerprint(doc):
+ return doc.content.get(KEY_FINGERPRINT_KEY, None)
def _address(doc):
@@ -60,40 +60,41 @@ class SoledadMaintenance(object):
def repair(self):
_, docs = yield self._soledad.get_all_docs()
- private_key_ids = self._key_ids_with_private_key(docs)
+ private_key_fingerprints = self._key_fingerprints_with_private_key(docs)
for doc in docs:
- if _is_key_doc(doc) and _key_id(doc) not in private_key_ids:
- logger.warn('Deleting doc %s for key %s of <%s>' % (doc.doc_id, _key_id(doc), _address(doc)))
+ if _is_key_doc(doc) and _key_fingerprint(doc) not in private_key_fingerprints:
+ logger.warn('Deleting doc %s for key %s of <%s>' % (doc.doc_id, _key_fingerprint(doc), _address(doc)))
yield self._soledad.delete_doc(doc)
- yield self._repair_missing_active_docs(docs, private_key_ids)
+ yield self._repair_missing_active_docs(docs, private_key_fingerprints)
@defer.inlineCallbacks
- def _repair_missing_active_docs(self, docs, private_key_ids):
- missing = self._missing_active_docs(docs, private_key_ids)
- for key_id in missing:
- emails = self._emails_for_key_id(docs, key_id)
+ def _repair_missing_active_docs(self, docs, private_key_fingerprints):
+ missing = self._missing_active_docs(docs, private_key_fingerprints)
+ for fingerprint in missing:
+ emails = self._emails_for_key_fingerprint(docs, fingerprint)
for email in emails:
- logger.warn('Re-creating active doc for key %s, email %s' % (key_id, email))
- yield self._soledad.create_doc_from_json(OpenPGPKey(email, key_id=key_id, private=False).get_active_json(email))
+ logger.warn('Re-creating active doc for key %s, email %s' % (fingerprint, email))
+ yield self._soledad.create_doc_from_json(OpenPGPKey(email, fingerprint=fingerprint, private=False).get_active_json())
- def _key_ids_with_private_key(self, docs):
- return [doc.content[KEY_ID_KEY] for doc in docs if _is_private_key_doc(doc)]
+ def _key_fingerprints_with_private_key(self, docs):
+ return [doc.content[KEY_FINGERPRINT_KEY] for doc in docs if _is_private_key_doc(doc)]
- def _missing_active_docs(self, docs, private_key_ids):
- active_doc_ids = self._active_docs_for_key_id(docs)
+ def _missing_active_docs(self, docs, private_key_fingerprints):
+ active_doc_ids = self._active_docs_for_key_fingerprint(docs)
- return set([private_key_id for private_key_id in private_key_ids if private_key_id not in active_doc_ids])
+ return set([private_key_fingerprint for private_key_fingerprint in private_key_fingerprints if private_key_fingerprint not in active_doc_ids])
- def _emails_for_key_id(self, docs, key_id):
+ def _emails_for_key_fingerprint(self, docs, fingerprint):
for doc in docs:
- if _is_private_key_doc(doc) and _key_id(doc) == key_id:
+ if _is_private_key_doc(doc) and _key_fingerprint(doc) == fingerprint:
email = _address(doc)
+ if email is None:
+ return []
if isinstance(email, list):
return email
- else:
- return [email]
+ return [email]
- def _active_docs_for_key_id(self, docs):
- return [doc.content[KEY_ID_KEY] for doc in docs if _is_active_key_doc(doc) and _is_public_key(doc)]
+ def _active_docs_for_key_fingerprint(self, docs):
+ return [doc.content[KEY_FINGERPRINT_KEY] for doc in docs if _is_active_key_doc(doc) and _is_public_key(doc)]
diff --git a/service/pixelated/adapter/search/__init__.py b/service/pixelated/adapter/search/__init__.py
index e137b392..3ec6532b 100644
--- a/service/pixelated/adapter/search/__init__.py
+++ b/service/pixelated/adapter/search/__init__.py
@@ -30,6 +30,7 @@ from whoosh.writing import AsyncWriter
from whoosh import sorting
from pixelated.support.functional import unique, to_unicode
import traceback
+from pixelated.support import date
class SearchEngine(object):
@@ -128,7 +129,7 @@ class SearchEngine(object):
index_data = {
'sender': self._empty_string_to_none(header.get('from', '')),
'subject': self._empty_string_to_none(header.get('subject', '')),
- 'date': self._format_utc_integer(header.get('date', '')),
+ 'date': self._format_utc_integer(header.get('date', date.mail_date_now())),
'to': self._format_recipient(header, 'to'),
'cc': self._format_recipient(header, 'cc'),
'bcc': self._format_recipient(header, 'bcc'),
diff --git a/service/pixelated/resources/keys_resource.py b/service/pixelated/resources/keys_resource.py
index d6f469fe..9075ab9e 100644
--- a/service/pixelated/resources/keys_resource.py
+++ b/service/pixelated/resources/keys_resource.py
@@ -17,7 +17,7 @@ class KeysResource(BaseResource):
if key.private:
respond_json_deferred(None, request, status_code=401)
else:
- respond_json_deferred(key.get_json(), request)
+ respond_json_deferred(key.get_active_json(), request)
def key_not_found(_):
respond_json_deferred(None, request, status_code=404)
diff --git a/service/pixelated/resources/logout_resource.py b/service/pixelated/resources/logout_resource.py
index 344ad2e9..01092b05 100644
--- a/service/pixelated/resources/logout_resource.py
+++ b/service/pixelated/resources/logout_resource.py
@@ -1,5 +1,8 @@
+from twisted.web.server import NOT_DONE_YET
+
from pixelated.resources import BaseResource
from twisted.web import util
+from twisted.internet import defer
from pixelated.resources.login_resource import LoginResource
@@ -8,9 +11,19 @@ class LogoutResource(BaseResource):
BASE_URL = "logout"
isLeaf = True
- def render_POST(self, request):
+ @defer.inlineCallbacks
+ def _execute_logout(self, request):
session = self.get_session(request)
- self._services_factory.log_out_user(session.user_uuid)
+ yield self._services_factory.log_out_user(session.user_uuid)
session.expire()
- return util.redirectTo("/%s" % LoginResource.BASE_URL, request)
+ def render_POST(self, request):
+ def _redirect_to_login(_):
+ content = util.redirectTo("/%s" % LoginResource.BASE_URL, request)
+ request.write(content)
+ request.finish()
+
+ d = self._execute_logout(request)
+ d.addCallback(_redirect_to_login)
+
+ return NOT_DONE_YET
diff --git a/service/requirements.txt b/service/requirements.txt
index 1966f09a..95a0f4f8 100644
--- a/service/requirements.txt
+++ b/service/requirements.txt
@@ -8,11 +8,11 @@ requests==2.0.0
srp==1.0.4
whoosh==2.5.7
pycryptopp
--e 'git+https://github.com/pixelated-project/leap_pycommon.git@develop#egg=leap.common'
--e 'git+https://github.com/pixelated-project/leap_auth.git#egg=leap.auth'
--e 'git+https://github.com/pixelated-project/soledad.git@develop#egg=leap.soledad.common&subdirectory=common/'
--e 'git+https://github.com/pixelated-project/soledad.git@develop#egg=leap.soledad.client&subdirectory=client/'
--e 'git+https://github.com/pixelated-project/soledad.git@develop#egg=leap.soledad.server&subdirectory=server/'
--e 'git+https://github.com/pixelated-project/keymanager.git@develop#egg=leap.keymanager'
--e 'git+https://github.com/pixelated-project/leap_mail.git@develop#egg=leap.mail'
+-e 'git+https://github.com/pixelated/leap_pycommon.git@develop#egg=leap.common'
+-e 'git+https://github.com/pixelated/leap_auth.git#egg=leap.auth'
+-e 'git+https://github.com/pixelated/soledad.git@develop#egg=leap.soledad.common&subdirectory=common/'
+-e 'git+https://github.com/pixelated/soledad.git@develop#egg=leap.soledad.client&subdirectory=client/'
+-e 'git+https://github.com/pixelated/soledad.git@develop#egg=leap.soledad.server&subdirectory=server/'
+-e 'git+https://github.com/pixelated/keymanager.git@develop#egg=leap.keymanager'
+-e 'git+https://github.com/pixelated/leap_mail.git@develop#egg=leap.mail'
-e .
diff --git a/service/test/unit/adapter/mailstore/maintenance/test_soledad_maintenance.py b/service/test/unit/adapter/mailstore/maintenance/test_soledad_maintenance.py
index e46d6864..be73af93 100644
--- a/service/test/unit/adapter/mailstore/maintenance/test_soledad_maintenance.py
+++ b/service/test/unit/adapter/mailstore/maintenance/test_soledad_maintenance.py
@@ -26,7 +26,7 @@ logging.getLogger('pixelated.adapter.mailstore.maintenance').addHandler(logging.
SOME_EMAIL_ADDRESS = 'foo@example.tld'
-SOME_KEY_ID = '4914254E384E264C'
+SOME_FINGERPRINT = '4914254E384E264C'
class TestSoledadMaintenance(unittest.TestCase):
@@ -42,8 +42,8 @@ class TestSoledadMaintenance(unittest.TestCase):
@defer.inlineCallbacks
def test_repair_delete_public_key_active_docs(self):
soledad = mock()
- key = self._public_key(SOME_EMAIL_ADDRESS, SOME_KEY_ID)
- active_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json(SOME_EMAIL_ADDRESS))
+ key = self._public_key(SOME_EMAIL_ADDRESS, SOME_FINGERPRINT)
+ active_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json())
when(soledad).get_all_docs().thenReturn(defer.succeed((1, [active_doc])))
yield SoledadMaintenance(soledad).repair()
@@ -53,8 +53,8 @@ class TestSoledadMaintenance(unittest.TestCase):
@defer.inlineCallbacks
def test_repair_delete_public_key_docs(self):
soledad = mock()
- key = self._public_key(SOME_EMAIL_ADDRESS, SOME_KEY_ID)
- active_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json(SOME_EMAIL_ADDRESS))
+ key = self._public_key(SOME_EMAIL_ADDRESS, SOME_FINGERPRINT)
+ active_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json())
key_doc = SoledadDocument(doc_id='some_doc', json=key.get_json())
when(soledad).get_all_docs().thenReturn(defer.succeed((1, [key_doc, active_doc])))
@@ -66,9 +66,9 @@ class TestSoledadMaintenance(unittest.TestCase):
@defer.inlineCallbacks
def test_repair_keeps_active_and_key_doc_if_private_key_exists(self):
soledad = mock()
- key = self._public_key(SOME_EMAIL_ADDRESS, SOME_KEY_ID)
- private_key = self._private_key(SOME_EMAIL_ADDRESS, SOME_KEY_ID)
- active_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json(SOME_EMAIL_ADDRESS))
+ key = self._public_key(SOME_EMAIL_ADDRESS, SOME_FINGERPRINT)
+ private_key = self._private_key(SOME_EMAIL_ADDRESS, SOME_FINGERPRINT)
+ active_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json())
key_doc = SoledadDocument(doc_id='some_doc', json=key.get_json())
private_key_doc = SoledadDocument(doc_id='some_doc', json=private_key.get_json())
when(soledad).get_all_docs().thenReturn(defer.succeed((1, [key_doc, active_doc, private_key_doc])))
@@ -82,8 +82,8 @@ class TestSoledadMaintenance(unittest.TestCase):
@defer.inlineCallbacks
def test_repair_only_deletes_key_docs(self):
soledad = mock()
- key = self._public_key(SOME_EMAIL_ADDRESS, SOME_KEY_ID)
- key_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json(SOME_EMAIL_ADDRESS))
+ key = self._public_key(SOME_EMAIL_ADDRESS, SOME_FINGERPRINT)
+ key_doc = SoledadDocument(doc_id='some_doc', json=key.get_active_json())
other_doc = SoledadDocument(doc_id='something', json='{}')
when(soledad).get_all_docs().thenReturn(defer.succeed((1, [key_doc, other_doc])))
@@ -95,19 +95,19 @@ class TestSoledadMaintenance(unittest.TestCase):
def test_repair_recreates_public_key_active_doc_if_necessary(self):
soledad = mock()
- private_key = self._private_key(SOME_EMAIL_ADDRESS, SOME_KEY_ID)
- private_key_doc = SoledadDocument(doc_id='some_doc', json=private_key.get_json())
+ private_key = self._private_key(SOME_EMAIL_ADDRESS, SOME_FINGERPRINT)
+ private_key_doc = SoledadDocument(doc_id='some_doc', json=private_key.get_active_json())
when(soledad).get_all_docs().thenReturn(defer.succeed((1, [private_key_doc])))
yield SoledadMaintenance(soledad).repair()
- verify(soledad).create_doc_from_json('{"key_id": "4914254E384E264C", "tags": ["keymanager-active"], "type": "OpenPGPKey-active", "private": false, "address": "foo@example.tld"}')
+ verify(soledad).create_doc_from_json('{"encr_used": false, "sign_used": false, "validation": "Weak_Chain", "version": 1, "address": "foo@example.tld", "last_audited_at": 0, "fingerprint": "4914254E384E264C", "type": "OpenPGPKey-active", "private": false, "tags": ["keymanager-active"]}')
- def _public_key(self, address, keyid):
- return self._gpgkey(address, keyid, private=False)
+ def _public_key(self, address, fingerprint):
+ return self._gpgkey(address, fingerprint, private=False)
- def _private_key(self, address, keyid):
- return self._gpgkey(address, keyid, private=True)
+ def _private_key(self, address, fingerprint):
+ return self._gpgkey(address, fingerprint, private=True)
- def _gpgkey(self, address, keyid, private=False):
- return OpenPGPKey(address, key_id=keyid, private=private)
+ def _gpgkey(self, address, fingerprint, private=False):
+ return OpenPGPKey(address, fingerprint=fingerprint, private=private)
diff --git a/service/test/unit/adapter/search/test_search.py b/service/test/unit/adapter/search/test_search.py
index 198a818b..be37257c 100644
--- a/service/test/unit/adapter/search/test_search.py
+++ b/service/test/unit/adapter/search/test_search.py
@@ -20,6 +20,7 @@ import unittest
from pixelated.adapter.mailstore.leap_mailstore import LeapMail
from pixelated.adapter.search import SearchEngine
from tempdir import TempDir
+from datetime import datetime
from test.support import test_helper
from pixelated.support.functional import to_unicode
@@ -57,6 +58,7 @@ class SearchEngineTest(unittest.TestCase):
'To': '=?utf-8?b?IsOEw7zDtiDDlsO8w6QiIDxmb2xrZXJAcGl4ZWxhdGVkLXByb2plY3Qub3Jn?=\n =?utf-8?b?PiwgRsO2bGtlciA8Zm9sa2VyQHBpeGVsYXRlZC1wcm9qZWN0Lm9yZz4=?=',
'Cc': '=?utf-8?b?IsOEw7zDtiDDlsO8w6QiIDxmb2xrZXJAcGl4ZWxhdGVkLXByb2plY3Qub3Jn?=\n =?utf-8?b?PiwgRsO2bGtlciA8Zm9sa2VyQHBpeGVsYXRlZC1wcm9qZWN0Lm9yZz4=?=',
'Subject': 'Some test mail',
+ 'Date': str(datetime.now())
}
# when
@@ -75,6 +77,7 @@ class SearchEngineTest(unittest.TestCase):
'To': '=?utf-8?b?IsOEw7zDtiDDlsO8w6QiIDxmb2xrZXJAcGl4ZWxhdGVkLXByb2plY3Qub3Jn?=\n =?utf-8?b?PiwgRsO2bGtlciA8Zm9sa2VyQHBpeGVsYXRlZC1wcm9qZWN0Lm9yZz4=?=',
'Cc': '=?utf-8?b?IsOEw7zDtiDDlsO8w6QiIDxmb2xrZXJAcGl4ZWxhdGVkLXByb2plY3Qub3Jn?=\n =?utf-8?b?PiwgRsO2bGtlciA8Zm9sa2VyQHBpeGVsYXRlZC1wcm9qZWN0Lm9yZz4=?=',
'Subject': 'Some test mail',
+ 'Date': str(datetime.now())
}
body = "When doing the search, it's not possible to find words with graphical accents, e.g.: 'coração', 'é', 'Fièvre', La Pluie d'été, 'não'."
@@ -100,6 +103,7 @@ class SearchEngineTest(unittest.TestCase):
'To': '=?utf-8?b?IsOEw7zDtiDDlsO8w6QiIDxmb2xrZXJAcGl4ZWxhdGVkLXByb2plY3Qub3Jn?=\n =?utf-8?b?PiwgRsO2bGtlciA8Zm9sa2VyQHBpeGVsYXRlZC1wcm9qZWN0Lm9yZz4=?=',
'Cc': '=?utf-8?b?IsOEw7zDtiDDlsO8w6QiIDxmb2xrZXJAcGl4ZWxhdGVkLXByb2plY3Qub3Jn?=\n =?utf-8?b?PiwgRsO2bGtlciA8Zm9sa2VyQHBpeGVsYXRlZC1wcm9qZWN0Lm9yZz4=?=',
'Subject': 'Some test mail',
+ 'Date': str(datetime.now())
}
body = "When doing the search, 您好 أهلا"
diff --git a/service/test/unit/resources/test_keys_resources.py b/service/test/unit/resources/test_keys_resources.py
index 6aa822e1..2bf53cb4 100644
--- a/service/test/unit/resources/test_keys_resources.py
+++ b/service/test/unit/resources/test_keys_resources.py
@@ -44,20 +44,16 @@ class TestKeysResource(unittest.TestCase):
d = self.web.get(request)
expected = {
- "tags": ["keymanager-key"],
- "fingerprint": '',
- "private": False,
- 'sign_used': False,
- 'refreshed_at': 0,
- "expiry_date": 0,
- "address": 'some@key',
- 'encr_used': False,
- 'last_audited_at': 0,
- 'key_data': '',
- 'length': 0,
- 'key_id': '',
- 'validation': 'Weak_Chain',
- 'type': 'OpenPGPKey',
+ u'address': u'some@key',
+ u'encr_used': False,
+ u'fingerprint': u'',
+ u'last_audited_at': 0,
+ u'private': False,
+ u'sign_used': False,
+ u'tags': [u'keymanager-active'],
+ u'type': u'OpenPGPKey-active',
+ u'validation': u'Weak_Chain',
+ u'version': 1,
}
def assert_response(_):
diff --git a/service/test/unit/resources/test_logout_resources.py b/service/test/unit/resources/test_logout_resources.py
index 6246eeb9..312d2ba4 100644
--- a/service/test/unit/resources/test_logout_resources.py
+++ b/service/test/unit/resources/test_logout_resources.py
@@ -1,6 +1,6 @@
-from mock import patch
-from mockito import mock, verify
+from mock import patch, MagicMock
from twisted.trial import unittest
+from twisted.internet import defer
from twisted.web.error import UnsupportedMethod
from twisted.web.test.requesthelper import DummyRequest
@@ -10,8 +10,9 @@ from test.unit.resources import DummySite
class TestLogoutResource(unittest.TestCase):
def setUp(self):
- self.services_factory = mock()
+ self.services_factory = MagicMock()
self.resource = LogoutResource(self.services_factory)
+ self.services_factory.log_out_user.return_value = defer.succeed(None)
self.web = DummySite(self.resource)
@patch('twisted.web.util.redirectTo')
@@ -19,14 +20,16 @@ class TestLogoutResource(unittest.TestCase):
request = DummyRequest(['/logout'])
request.method = 'POST'
- mock_redirect.return_value = 'haha'
+ session = self.resource.get_session(request)
+ session.expire = MagicMock()
+ mock_redirect.return_value = 'some redirect response'
d = self.web.get(request)
def expire_session_and_redirect(_):
session = self.resource.get_session(request)
- self.assertFalse(session.is_logged_in())
- verify(self.services_factory).log_out_user(session.user_uuid)
+ self.services_factory.log_out_user.assert_called_once_with(session.user_uuid)
+ session.expire.assert_called_once_with()
mock_redirect.assert_called_once_with('/login', request)
d.addCallback(expire_session_and_redirect)
diff --git a/web-ui/app/images/logo.svg b/web-ui/app/images/logo.svg
new file mode 100644
index 00000000..6c2d8989
--- /dev/null
+++ b/web-ui/app/images/logo.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<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 627.3 612 171.1" enable-background="new 30.4 627.3 612 171.1" xml:space="preserve">
+<g>
+ <path fill="#3E3B38" d="M30.4,669.9v85.8l75.5,42.7l75.5-42.7v-85.8l-75.5-42.7L30.4,669.9z M102.7,767.4l-44-24.3v-52.6l44,25
+ V767.4z M152.5,743l-43.4,24.3v-51.8l43.4-25.4V743z M152.5,683.1l-46.7,27.8l-47.2-27.8l47.2-25.4L152.5,683.1z"/>
+ <path fill="#3E3B38" d="M233.8,678.3h-24v71.2h16.2v-26.5h7.8c14,0,24.3-8,24.3-22.9C258.1,685.6,247.6,678.3,233.8,678.3z
+ M230.6,710.2h-4.6v-18.8h4.6c6.5,0,12.5,2.2,12.5,9.5C243,708.1,237.1,710.2,230.6,710.2z"/>
+ <rect x="263.5" y="678.3" fill="#3E3B38" width="16.2" height="71.2"/>
+ <polygon fill="#3E3B38" points="350.4,678.3 330.1,678.3 316.9,697.7 303.7,678.3 284.6,678.3 307,711.1 282.6,749.5 302.9,749.5
+ 316.9,725.3 331,749.5 352.1,749.5 326.9,711.1 "/>
+ <polygon fill="#3E3B38" points="354.7,749.5 395.5,749.5 395.5,735.2 370.9,735.2 370.9,721 394.4,721 394.4,706.6 370.9,706.6
+ 370.9,692.5 395.5,692.5 395.5,678.3 354.7,678.3 "/>
+ <path fill="#3E3B38" d="M456.1,678.3l-22.9,57h-15.9v-57h-16.2v71.2h26.5h14.3h3.2l5.4-14.3h27l5.4,14.3h17.5l-28.9-71.2H456.1z
+ M455.7,721l7.8-20.7h0.2l7.8,20.7H455.7z"/>
+ <polygon fill="#3E3B38" points="486.4,692.5 503.4,692.5 503.4,749.5 519.6,749.5 519.6,692.5 536.6,692.5 536.6,678.3
+ 486.4,678.3 "/>
+ <polygon fill="#3E3B38" points="542,749.5 582.8,749.5 582.8,735.2 558.4,735.2 558.4,721 581.9,721 581.9,706.6 558.4,706.6
+ 558.4,692.5 582.8,692.5 582.8,678.3 542,678.3 "/>
+ <path fill="#3E3B38" d="M606.5,678.3h-17.9v71.2h17.9c19.7,0,35.9-14.9,35.9-35.6C642.4,693.1,625.9,678.3,606.5,678.3z M607,735
+ h-2.4v-42.1h2.4c12.1,0,20.3,9.1,20.3,21.1C627.3,725.8,619.1,735,607,735z"/>
+</g>
+<polygon id="clock1" fill="#3E3B38" points="105.8,657.8 105.8,628 105.8,627.3 181.4,669.9 152.5,683.1 "/>
+<polygon id="clock2" fill="#3E3B38" points="152.5,683.1 181.4,669.9 181.4,755.7 152.5,743 "/>
+<polygon id="clock3" fill="#3E3B38" points="105.9,798.3 105.9,769 152.5,743 181.4,755.7 "/>
+<polygon id="clock4" fill="#3E3B38" points="58.7,743.1 105.9,769 105.9,798.3 30.4,755.7 "/>
+<polygon id="clock5" fill="#3E3B38" points="30.4,669.9 58.6,683.1 58.7,743.1 30.4,755.7 "/>
+<polygon id="clock6" fill="#3E3B38" points="105.8,628 105.8,657.8 58.6,683.1 30.4,669.9 105.8,627.3 "/>
+</svg>
diff --git a/web-ui/app/js/mail_view/ui/mail_view.js b/web-ui/app/js/mail_view/ui/mail_view.js
index 8465b45a..dfc57585 100644
--- a/web-ui/app/js/mail_view/ui/mail_view.js
+++ b/web-ui/app/js/mail_view/ui/mail_view.js
@@ -76,6 +76,7 @@ define(
this.trigger(document, events.search.highlightResults, {where: '.bodyArea'});
this.trigger(document, events.search.highlightResults, {where: '.subjectArea'});
this.trigger(document, events.search.highlightResults, {where: '.msg-header .recipients'});
+ this.trigger(document, events.ui.replyBox.showReplyContainer);
this.attachTagCompletion(this.attr.mail);
diff --git a/web-ui/app/js/mail_view/ui/reply_section.js b/web-ui/app/js/mail_view/ui/reply_section.js
index 46dfe863..cbe64205 100644
--- a/web-ui/app/js/mail_view/ui/reply_section.js
+++ b/web-ui/app/js/mail_view/ui/reply_section.js
@@ -36,7 +36,8 @@ define(
replyAllButton: '#reply-all-button',
forwardButton: '#forward-button',
replyBox: '#reply-box',
- replyType: 'reply'
+ replyType: 'reply',
+ replyContainer: '.reply-container'
});
this.showReply = function() {
@@ -64,9 +65,7 @@ define(
this.checkForDraftReply = function() {
this.render();
- this.select('replyButton').hide();
- this.select('replyAllButton').hide();
- this.select('forwardButton').hide();
+ this.hideContainer();
this.trigger(document, events.mail.draftReply.want, {ident: this.attr.ident});
};
@@ -76,11 +75,13 @@ define(
};
this.showDraftReply = function(ev, data) {
+ this.showContainer();
this.hideButtons();
ReplyBox.attachTo(this.select('replyBox'), { mail: data.mail, draftReply: true });
};
this.showReplyComposeBox = function (ev, data) {
+ this.showContainer();
this.hideButtons();
if(this.attr.replyType === 'forward') {
ForwardBox.attachTo(this.select('replyBox'), { mail: data.mail });
@@ -89,6 +90,14 @@ define(
}
};
+ this.hideContainer = function() {
+ this.select('replyContainer').hide();
+ };
+
+ this.showContainer = function() {
+ this.select('replyContainer').show();
+ };
+
this.hideButtons = function() {
this.select('replyButton').hide();
this.select('replyAllButton').hide();
@@ -96,6 +105,7 @@ define(
};
this.showButtons = function () {
+ this.showContainer();
this.select('replyBox').empty();
this.select('replyButton').show();
this.select('replyAllButton').show();
@@ -109,7 +119,7 @@ define(
this.on(this, events.mail.here, this.showReplyComposeBox);
this.on(document, events.dispatchers.rightPane.clear, this.teardown);
- this.on(document, events.mail.draftReply.notFound, this.showButtons);
+ this.on(document, events.ui.replyBox.showReplyContainer, this.showContainer);
this.on(document, events.mail.draftReply.here, this.showDraftReply);
this.checkForDraftReply();
diff --git a/web-ui/app/js/page/events.js b/web-ui/app/js/page/events.js
index 1ec27c46..7a0dbf9d 100644
--- a/web-ui/app/js/page/events.js
+++ b/web-ui/app/js/page/events.js
@@ -79,7 +79,8 @@ define(function () {
},
replyBox: {
showReply: 'ui:replyBox:showReply',
- showReplyAll: 'ui:replyBox:showReplyAll'
+ showReplyAll: 'ui:replyBox:showReplyAll',
+ showReplyContainer: 'ui:replyBox:showReplyContainer',
},
recipients: {
entered: 'ui:recipients:entered',
diff --git a/web-ui/app/scss/_mixins.scss b/web-ui/app/scss/_mixins.scss
index a623366d..5bb84105 100644
--- a/web-ui/app/scss/_mixins.scss
+++ b/web-ui/app/scss/_mixins.scss
@@ -88,6 +88,7 @@
}
ul.tags {
+ margin-bottom: 0;
li {
font-size: 0.6rem;
background-color: lighten($action_buttons, 12);
diff --git a/web-ui/app/scss/_read.scss b/web-ui/app/scss/_read.scss
index 7943d584..2c079408 100644
--- a/web-ui/app/scss/_read.scss
+++ b/web-ui/app/scss/_read.scss
@@ -65,7 +65,7 @@
}
.bodyArea {
- padding: 15px 30px 0 30px;
+ padding: 10px 30px 0 30px;
}
.attachmentsAreaWrap {
diff --git a/web-ui/app/scss/_security.scss b/web-ui/app/scss/_security.scss
index 8e9a6b5d..ff36cb3e 100644
--- a/web-ui/app/scss/_security.scss
+++ b/web-ui/app/scss/_security.scss
@@ -3,10 +3,11 @@
clear: both;
span {
display: inline-block;
- padding: 2px 5px;
+ padding: 2px 6px;
white-space: nowrap;
background: $success;
color: $white;
+ border-radius: 12px;
&:before {
font-family: FontAwesome;
}
diff --git a/web-ui/test/spec/mail_view/ui/mail_view.spec.js b/web-ui/test/spec/mail_view/ui/mail_view.spec.js
index ae874621..9ed56023 100644
--- a/web-ui/test/spec/mail_view/ui/mail_view.spec.js
+++ b/web-ui/test/spec/mail_view/ui/mail_view.spec.js
@@ -29,6 +29,11 @@ describeComponent('mail_view/ui/mail_view', function () {
expect(openNoMessageSelectedEvent).toHaveBeenTriggeredOn(document);
});
+ it('should open reply container', function () {
+ var showContainerEvent = spyOnEvent(document, Pixelated.events.ui.replyBox.showReplyContainer);
+ this.component.displayMail({}, testData);
+ expect(showContainerEvent).toHaveBeenTriggeredOn(document);
+ });
it('removes the tag from the mail when the tag label is clicked', function() {
var updateSpy = spyOnEvent(document, Pixelated.events.mail.tags.update);
diff --git a/web-ui/test/spec/mail_view/ui/reply_section.spec.js b/web-ui/test/spec/mail_view/ui/reply_section.spec.js
index 9cdf7405..00709684 100644
--- a/web-ui/test/spec/mail_view/ui/reply_section.spec.js
+++ b/web-ui/test/spec/mail_view/ui/reply_section.spec.js
@@ -5,6 +5,18 @@ describeComponent('mail_view/ui/reply_section', function () {
this.setupComponent();
});
+ describe('show/hide reply container', function () {
+ it('should hide reply container until mail data is loaded', function () {
+ this.component.checkForDraftReply();
+ expect(this.component.select('replyContainer')).toBeHidden();
+ });
+
+ it('should show reply container when mail data is loaded', function () {
+ this.component.trigger(document, Pixelated.events.ui.replyBox.showReplyContainer);
+ expect(this.component.select('replyContainer')).not.toBeHidden();
+ });
+ });
+
describe('clicking reply buttons', function() {
var mailWantEvent, expectEventData;
@@ -45,6 +57,7 @@ describeComponent('mail_view/ui/reply_section', function () {
this.component.attr.replyType = 'reply';
this.component.trigger(this.component, Pixelated.events.mail.here, { mail: mailData });
+ expect(this.component.select('replyContainer')).not.toBeHidden();
expect(ReplyBox.attachTo).toHaveBeenCalledWith(jasmine.any(Object), {
mail: mailData,
replyType: 'reply'
@@ -55,6 +68,7 @@ describeComponent('mail_view/ui/reply_section', function () {
this.component.attr.replyType = 'replyall';
this.component.trigger(this.component, Pixelated.events.mail.here, { mail: mailData });
+ expect(this.component.select('replyContainer')).not.toBeHidden();
expect(ReplyBox.attachTo).toHaveBeenCalledWith(jasmine.any(Object), {
mail: mailData,
replyType: 'replyall'
@@ -65,6 +79,7 @@ describeComponent('mail_view/ui/reply_section', function () {
this.component.attr.replyType = 'forward';
this.component.trigger(this.component, Pixelated.events.mail.here, { mail: mailData });
+ expect(this.component.select('replyContainer')).not.toBeHidden();
expect(ForwardBox.attachTo).toHaveBeenCalledWith(jasmine.any(Object), {
mail: mailData
});
@@ -87,6 +102,7 @@ describeComponent('mail_view/ui/reply_section', function () {
$(document).trigger(Pixelated.events.ui.composeBox.trashReply);
+ expect(this.component.select('replyContainer')).not.toBeHidden();
expect(this.component.select('replyButton')).not.toBeHidden();
expect(this.component.select('replyAllButton')).not.toBeHidden();
expect(this.component.select('forwardButton')).not.toBeHidden();