summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md40
-rw-r--r--Vagrantfile4
-rw-r--r--osx_setup.sh2
-rw-r--r--provisioning/modules/pixelated/manifests/source/install_useragent.pp12
-rw-r--r--service/pixelated/application.py8
-rw-r--r--service/pixelated/assets/_login_disclaimer_banner.html9
-rw-r--r--service/pixelated/assets/login.html2
-rw-r--r--service/pixelated/bitmask_libraries/session.py64
-rw-r--r--service/pixelated/config/arguments.py1
-rw-r--r--service/pixelated/resources/attachments_resource.py5
-rw-r--r--service/pixelated/resources/login_resource.py30
-rw-r--r--service/pixelated/resources/root_resource.py4
-rw-r--r--service/test/integration/test_retrieve_attachment.py12
-rw-r--r--service/test/support/integration/app_test_client.py9
-rw-r--r--service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py23
-rw-r--r--service/test/unit/bitmask_libraries/test_smtp_client_certificate.py66
-rw-r--r--service/test/unit/resources/test_login_resource.py31
-rw-r--r--web-ui/app/templates/mails/full_view.hbs2
-rw-r--r--web-ui/test/spec/mail_view/ui/mail_view.spec.js2
-rw-r--r--web-ui/test/test_data.js3
20 files changed, 263 insertions, 66 deletions
diff --git a/README.md b/README.md
index 2abf95b3..9cb7f215 100644
--- a/README.md
+++ b/README.md
@@ -26,22 +26,34 @@ $ cd pixelated-user-agent
$ vagrant up
```
-4) Log into the VM:
+4) (optional) If you want to run the tests in your IDE on your host machine outside of vagrant, set up your python virtualenv
+
+```
+$ pip install virtualenv setuptools
+$ cd ~
+$ virtualenv pixelated
+$ virtualenv -p [PATH/TO/YOUR/PYTHON/EXECUTABLE] pixelated
+$ source ~/.virtualenv/pixelated/bin/activate
+```
+
+5) (optional) If you want to run the tests in your IDE on your host machine outside of vagrant, there's a bug in a LEAP library that can't handle symlinks to your local GPG installation. To fix it, add the path to your GPG binary to your $PATH so that it is found before the symlink in `/usr/local/bin` (or similar)
+
+6) Log into the VM:
```
$ vagrant ssh
```
-5) Setup the project (downloads a few hundred more megabytes):
+7) Setup the project (downloads a few hundred more megabytes):
```
$ cd /vagrant/service
$ ./go setup
```
-6) Register with a LEAP provider. You can create a developer account at our [Dev Provider](https://dev.pixelated-project.org/). Please contact us at team@pixelated-project.org for an invite code.
+8) Register with a LEAP provider. You can create a developer account at our [Dev Provider](https://dev.pixelated-project.org/). Please contact us at team@pixelated-project.org for an invite code.
-7) Run the user agent:
+9) Run the user agent:
```
$ pixelated-user-agent --host 0.0.0.0
@@ -56,11 +68,11 @@ Type your password:
******** (the one you created in previous step)
```
-8) Connect to the provider using your credentials. If the user agent starts up successfully, you will not see any other output.
+10) Connect to the provider using your credentials. If the user agent starts up successfully, you will not see any other output.
-9) Go to [localhost:3333](http://localhost:3333/). You should see a loading screen for a few seconds, then your inbox. If it sticks on the loading screen, check your terminal for errors, then [get help](https://pixelated-project.org/faq/#contact-the-project).
+11) Go to [localhost:3333](http://localhost:3333/). You should see a loading screen for a few seconds, then your inbox. If it sticks on the loading screen, check your terminal for errors, then [get help](https://pixelated-project.org/faq/#contact-the-project).
-10) If you like console output, you can also run the tests to see if everything went according to plan.
+12) If you like console output, you can also run the tests to see if everything went according to plan.
To run the backend tests:
@@ -83,7 +95,19 @@ To run the functional tests:
(user-agent-venv)vagrant@jessie:/vagrant/service$ ./go functional
```
-11. You're all set! We've prepared [a couple of issues labeled "Beginner"](https://github.com/pixelated/pixelated-user-agent/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3ABeginners+) that are a good place to dive into the project. Happy Hacking!
+13) You're all set! We've prepared [a couple of issues labeled "Beginner"](https://github.com/pixelated/pixelated-user-agent/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3ABeginners+) that are a good place to dive into the project. Happy Hacking!
+
+## How do I see the result of my changes?
+
+For all **Python changes**, you will need to kill (Ctrl-C) the server and run `$ pixelated-user-agent --host 0.0.0.0` again.
+
+For most **JavaScript** or **HTML changes**, you will just need to reload the browser.
+
+For most **CSS or Handlebars templates changes**, you will also need to run: `$ cd /vagrant/web-ui && ./go build`
+
+## I think I might be able to hack together a quick-and-dirty lo-fi solution for the issue I’m working with… what do I do?
+
+Do it the easy way first, and submit a pull request as a “work in progress” as soon as you have a quick-and-dirty solution (or even an unfinished solution) — that means you can get feedback from the other developers about whether you’re heading in the right direction sooner rather than later. Include “WIP” (work in progress) in the description of your pull request and ask for review, or feedback on anything specific.
# Further Notes
diff --git a/Vagrantfile b/Vagrantfile
index 4102c424..ba925079 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -22,10 +22,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
end
end
- if /mswin|mingw/ =~ RUBY_PLATFORM
- config.vm.synced_folder ".", "/vagrant", type: "rsync", rsync__exclude: ".git/"
- end
-
config.vm.provider "libvirt" do |v, override|
v.memory = 1024
override.vm.network :forwarded_port, guest: 3333, guest_ip: '127.0.0.1', host: 3333
diff --git a/osx_setup.sh b/osx_setup.sh
index 26b5b749..a8a37833 100644
--- a/osx_setup.sh
+++ b/osx_setup.sh
@@ -36,7 +36,7 @@ install_compass
install_npm
#setup backend
-brew install python # force brew install even if python is already install
+brew install python # force brew install even if python is already installed
export LDFLAGS=-L/usr/local/opt/openssl/lib
export LDFLAGS=-L/usr/local/opt/openssl/lib
pip install virtualenv
diff --git a/provisioning/modules/pixelated/manifests/source/install_useragent.pp b/provisioning/modules/pixelated/manifests/source/install_useragent.pp
index 8661f655..a2473b3a 100644
--- a/provisioning/modules/pixelated/manifests/source/install_useragent.pp
+++ b/provisioning/modules/pixelated/manifests/source/install_useragent.pp
@@ -1,3 +1,5 @@
+# install useragent in a virtualenv, deploy helper script
+# and make sure venv is activated on login
class pixelated::source::install_useragent {
$virtualenv_path = '/home/vagrant/user-agent-venv'
@@ -15,16 +17,16 @@ class pixelated::source::install_useragent {
timeout => 0
}
- file { '/home/vagrant/.activate_custom_node_modules.sh':
+ file { '/home/vagrant/activate_custom_node_modules.sh':
owner => 'vagrant',
- mode => '0600',
+ mode => '0755',
source => 'puppet:///modules/pixelated/activate_custom_node_modules.sh',
}
exec { 'add_custom_node_modules_to_bashrc':
- command => "/bin/bash -c 'echo \"source /home/vagrant/.activate_custom_node_modules.sh\" >> /home/vagrant/.bashrc'",
- unless => "/bin/grep \"source /home/vagrant/.activate_custom_node_modules.sh\" /home/vagrant/.bashrc",
+ command => "/bin/bash -c 'echo \"source /home/vagrant/user-agent-venv/bin/activate\" >> /home/vagrant/.bashrc'",
+ unless => "/bin/grep \"source /home/vagrant/user-agent-venv/bin/activate\" /home/vagrant/.bashrc",
user => 'vagrant',
- require => [Exec['install-pixelated'], File['/home/vagrant/.activate_custom_node_modules.sh']]
+ require => [Exec['install-pixelated'], File['/home/vagrant/activate_custom_node_modules.sh']]
}
}
diff --git a/service/pixelated/application.py b/service/pixelated/application.py
index b0f2d3fc..c7613fc6 100644
--- a/service/pixelated/application.py
+++ b/service/pixelated/application.py
@@ -166,24 +166,24 @@ def _start_in_multi_user_mode(args, root_resource, services_factory):
events_server.ensure_server()
config, provider = initialize_leap_provider(args.provider, args.leap_provider_cert, args.leap_provider_cert_fingerprint, args.leap_home)
- protected_resource = set_up_protected_resources(root_resource, provider, services_factory)
+ protected_resource = set_up_protected_resources(root_resource, provider, services_factory, banner=args.banner)
start_site(args, protected_resource)
reactor.getThreadPool().adjustPoolsize(5, 15)
return defer.succeed(None)
-def set_up_protected_resources(root_resource, provider, services_factory, checker=None):
+def set_up_protected_resources(root_resource, provider, services_factory, checker=None, banner=None):
if not checker:
checker = LeapPasswordChecker(provider)
session_checker = SessionChecker(services_factory)
- anonymous_resource = LoginResource(services_factory)
+ anonymous_resource = LoginResource(services_factory, disclaimer_banner=banner)
realm = PixelatedRealm(root_resource, anonymous_resource)
_portal = portal.Portal(realm, [checker, session_checker, AllowAnonymousAccess()])
protected_resource = PixelatedAuthSessionWrapper(_portal, root_resource, anonymous_resource, [])
anonymous_resource.set_portal(_portal)
- root_resource.initialize(_portal)
+ root_resource.initialize(_portal, disclaimer_banner=banner)
return protected_resource
diff --git a/service/pixelated/assets/_login_disclaimer_banner.html b/service/pixelated/assets/_login_disclaimer_banner.html
new file mode 100644
index 00000000..dfc63030
--- /dev/null
+++ b/service/pixelated/assets/_login_disclaimer_banner.html
@@ -0,0 +1,9 @@
+<div>
+ <ul class="accounts">
+ <h2>Some disclaimer</h2>
+ <li>
+ please supply the option --banner with an XML compatible file
+ <div>to override this default message</div>
+ </li>
+ </ul>
+</div>
diff --git a/service/pixelated/assets/login.html b/service/pixelated/assets/login.html
index d59ac4cd..a8585d05 100644
--- a/service/pixelated/assets/login.html
+++ b/service/pixelated/assets/login.html
@@ -28,7 +28,7 @@
</form>
</div>
<div class="disclaimer">
- Some disclaimer
+ <div t:render="disclaimer"></div>
</div>
</div>
</body>
diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py
index e217c286..9e908ce5 100644
--- a/service/pixelated/bitmask_libraries/session.py
+++ b/service/pixelated/bitmask_libraries/session.py
@@ -29,6 +29,7 @@ from leap.auth import SRPAuth
from .nicknym import NickNym
from .smtp import LeapSMTPConfig
from .soledad import SoledadFactory
+import leap.common.certs as leap_certs
from leap.common.events import (
register, unregister,
@@ -120,6 +121,40 @@ class LeapSession(object):
raise
+class SmtpClientCertificate(object):
+ def __init__(self, provider, auth, user_path):
+ self._provider = provider
+ self._auth = auth
+ self._user_path = user_path
+
+ def cert_path(self):
+ if not self._is_cert_already_downloaded() or self._should_redownload():
+ self._download_smtp_cert()
+
+ return self._smtp_client_cert_path()
+
+ def _is_cert_already_downloaded(self):
+ return os.path.exists(self._smtp_client_cert_path())
+
+ def _should_redownload(self):
+ return leap_certs.should_redownload(self._smtp_client_cert_path())
+
+ def _download_smtp_cert(self):
+ cert_path = self._smtp_client_cert_path()
+
+ if not os.path.exists(os.path.dirname(cert_path)):
+ os.makedirs(os.path.dirname(cert_path))
+
+ SmtpCertDownloader(self._provider, self._auth).download_to(cert_path)
+
+ def _smtp_client_cert_path(self):
+ return os.path.join(
+ self._user_path,
+ "providers",
+ self._provider.domain,
+ "keys", "client", "smtp.pem")
+
+
class SmtpCertDownloader(object):
def __init__(self, provider, auth):
@@ -127,12 +162,15 @@ class SmtpCertDownloader(object):
self._auth = auth
def download(self):
- cert_url = '%s/%s/cert' % (self._provider.api_uri, self._provider.api_version)
+ cert_url = '%s/%s/smtp_cert' % (self._provider.api_uri, self._provider.api_version)
cookies = {"_session_id": self._auth.session_id}
headers = {}
headers["Authorization"] = 'Token token="{0}"'.format(self._auth.token)
- response = requests.get(
+ params = {'address': self._auth.username}
+ response = requests.post(
cert_url,
+ params=params,
+ data=params,
verify=LeapCertificate(self._provider).provider_api_cert,
cookies=cookies,
timeout=self._provider.config.timeout_in_s,
@@ -188,26 +226,15 @@ class LeapSessionFactory(object):
mail_store = LeapMailStore(soledad)
nicknym = self._create_nicknym(account_email, auth.token, auth.uuid, soledad)
- self._download_smtp_cert(auth)
+ smtp_client_cert = self._download_smtp_cert(auth)
smtp_host, smtp_port = self._provider.smtp_info()
- smtp_config = LeapSMTPConfig(account_email, self._smtp_client_cert_path(), smtp_host, smtp_port)
+ smtp_config = LeapSMTPConfig(account_email, smtp_client_cert, smtp_host, smtp_port)
return LeapSession(self._provider, auth, mail_store, soledad, nicknym, smtp_config)
def _download_smtp_cert(self, auth):
- cert_path = self._smtp_client_cert_path()
-
- if not os.path.exists(os.path.dirname(cert_path)):
- os.makedirs(os.path.dirname(cert_path))
-
- SmtpCertDownloader(self._provider, auth).download_to(cert_path)
-
- def _smtp_client_cert_path(self):
- return os.path.join(
- self._config.leap_home,
- "providers",
- self._provider.domain,
- "keys", "client", "smtp.pem")
+ cert = SmtpClientCertificate(self._provider, auth, self._user_path(auth.uuid))
+ return cert.cert_path()
def _create_dir(self, path):
try:
@@ -221,6 +248,9 @@ class LeapSessionFactory(object):
def _create_nicknym(self, email_address, token, uuid, soledad):
return NickNym(self._provider, self._config, soledad, email_address, token, uuid)
+ def _user_path(self, user_uuid):
+ return os.path.join(self._config.leap_home, user_uuid)
+
def _soledad_path(self, user_uuid):
return os.path.join(self._config.leap_home, user_uuid, 'soledad')
diff --git a/service/pixelated/config/arguments.py b/service/pixelated/config/arguments.py
index 6a609b73..abd5d881 100644
--- a/service/pixelated/config/arguments.py
+++ b/service/pixelated/config/arguments.py
@@ -30,6 +30,7 @@ def parse_user_agent_args():
parser.add_argument('-sc', '--sslcert', metavar='<server.crt>', default=None, help='use specified file as web server\'s SSL certificate (when using the user-agent together with the pixelated-dispatcher)')
parser.add_argument('--multi-user', help='Run user agent in multi user mode', action='store_false', default=True, dest='single_user')
parser.add_argument('-p', '--provider', help='specify a provider for mutli-user mode', metavar='<provider host>', default=None, dest='provider')
+ parser.add_argument('--banner', help='banner file to show on login screen')
args = parser.parse_args()
diff --git a/service/pixelated/resources/attachments_resource.py b/service/pixelated/resources/attachments_resource.py
index 249d268a..086f6e4e 100644
--- a/service/pixelated/resources/attachments_resource.py
+++ b/service/pixelated/resources/attachments_resource.py
@@ -46,8 +46,9 @@ class AttachmentResource(Resource):
encoding = request.args.get('encoding', [None])[0]
filename = request.args.get('filename', [self.attachment_id])[0]
- request.setHeader(b'Content-Type', b'application/force-download')
- request.setHeader(b'Content-Disposition', bytes('attachment; filename=' + filename))
+ content_type = request.args.get('content_type', ['application/octet-stream'])[0]
+ request.setHeader(b'Content-Type', content_type)
+ request.setHeader(b'Content-Disposition', bytes('attachment; filename="' + filename + '"'))
d = self._send_attachment(encoding, filename, request)
d.addErrback(error_handler)
diff --git a/service/pixelated/resources/login_resource.py b/service/pixelated/resources/login_resource.py
index 1e1beb48..6f25fbcb 100644
--- a/service/pixelated/resources/login_resource.py
+++ b/service/pixelated/resources/login_resource.py
@@ -49,30 +49,48 @@ def _get_static_folder():
return static_folder
+class DisclaimerElement(Element):
+ loader = XMLFile(FilePath(os.path.join(_get_startup_folder(), '_login_disclaimer_banner.html')))
+
+ def __init__(self, banner):
+ super(DisclaimerElement, self).__init__()
+ self._set_loader(banner)
+
+ def _set_loader(self, banner):
+ if banner:
+ current_path = os.path.dirname(os.path.abspath(__file__))
+ banner_file_path = os.path.join(current_path, "..", "..", "..", banner)
+ self.loader = XMLFile(FilePath(banner_file_path))
+
+
class LoginWebSite(Element):
loader = XMLFile(FilePath(os.path.join(_get_startup_folder(), 'login.html')))
- def __init__(self, error_msg=None):
+ def __init__(self, error_msg=None, disclaimer_banner_file=None):
super(LoginWebSite, self).__init__()
self._error_msg = error_msg
+ self.disclaimer_banner_file = disclaimer_banner_file
@renderer
def error_msg(self, request, tag):
if self._error_msg is not None:
return tag(self._error_msg)
- else:
- return tag('')
+ return tag('')
+
+ @renderer
+ def disclaimer(self, request, tag):
+ return DisclaimerElement(self.disclaimer_banner_file).render(request)
class LoginResource(BaseResource):
BASE_URL = 'login'
- def __init__(self, services_factory, portal=None):
+ def __init__(self, services_factory, portal=None, disclaimer_banner=None):
BaseResource.__init__(self, services_factory)
self._static_folder = _get_static_folder()
self._startup_folder = _get_startup_folder()
- self._html_template = open(os.path.join(self._startup_folder, 'login.html')).read()
self._portal = portal
+ self._disclaimer_banner = disclaimer_banner
self.putChild('startup-assets', File(self._startup_folder))
def set_portal(self, portal):
@@ -92,7 +110,7 @@ class LoginResource(BaseResource):
return self._render_template(request)
def _render_template(self, request, error_msg=None):
- site = LoginWebSite(error_msg=error_msg)
+ site = LoginWebSite(error_msg=error_msg, disclaimer_banner_file=self._disclaimer_banner)
return renderElement(request, site)
def render_POST(self, request):
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index 61df0f39..6e619951 100644
--- a/service/pixelated/resources/root_resource.py
+++ b/service/pixelated/resources/root_resource.py
@@ -56,7 +56,7 @@ class RootResource(BaseResource):
return self
return Resource.getChild(self, path, request)
- def initialize(self, portal=None):
+ def initialize(self, portal=None, disclaimer_banner=None):
self.putChild('assets', File(self._static_folder))
self.putChild('keys', KeysResource(self._services_factory))
self.putChild(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory))
@@ -67,7 +67,7 @@ class RootResource(BaseResource):
self.putChild('mail', MailResource(self._services_factory))
self.putChild('feedback', FeedbackResource(self._services_factory))
self.putChild('user-settings', UserSettingsResource(self._services_factory))
- self.putChild(LoginResource.BASE_URL, LoginResource(self._services_factory, portal))
+ self.putChild(LoginResource.BASE_URL, LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner))
self.putChild(LogoutResource.BASE_URL, LogoutResource(self._services_factory))
self._mode = MODE_RUNNING
diff --git a/service/test/integration/test_retrieve_attachment.py b/service/test/integration/test_retrieve_attachment.py
index 7de03c59..4aaeadc2 100644
--- a/service/test/integration/test_retrieve_attachment.py
+++ b/service/test/integration/test_retrieve_attachment.py
@@ -27,22 +27,28 @@ from test.support.integration.soledad_test_base import SoledadTestBase
class RetrieveAttachmentTest(SoledadTestBase):
-
@defer.inlineCallbacks
def test_attachment_content_is_retrieved(self):
attachment_id, input_mail = self._create_mail_with_attachment()
yield self.mail_store.add_mail('INBOX', input_mail.as_string())
- attachment, req = yield self.get_attachment(attachment_id, 'base64')
+ requested_filename = "file name with space"
+ expected_content_type = 'text/plain'
+ expected_content_disposition = 'attachment; filename="file name with space"'
+
+ attachment, req = yield self.get_attachment(attachment_id, 'base64', filename=requested_filename, content_type=expected_content_type)
self.assertEqual(200, req.code)
self.assertEquals('pretend to be binary attachment data', attachment)
+ self.assertEquals(expected_content_disposition, req.outgoingHeaders['content-disposition'])
+ self.assertEquals(expected_content_type, req.outgoingHeaders['content-type'])
def _create_mail_with_attachment(self):
input_mail = MIMEMultipart()
input_mail.attach(MIMEText(u'a utf8 message', _charset='utf-8'))
attachment = MIMEApplication('pretend to be binary attachment data')
- attachment.add_header('Content-Disposition', 'attachment', filename='filename.txt')
+ attachment.add_header('Content-Disposition', 'attachment', filename='file name.txt')
+ attachment.add_header('Content-Type', 'text/plain')
input_mail.attach(attachment)
attachment_id = 'B5B4ED80AC3B894523D72E375DACAA2FC6606C18EDF680FE95903086C8B5E14A'
return attachment_id, input_mail
diff --git a/service/test/support/integration/app_test_client.py b/service/test/support/integration/app_test_client.py
index 99f4ebc7..8ab58397 100644
--- a/service/test/support/integration/app_test_client.py
+++ b/service/test/support/integration/app_test_client.py
@@ -322,8 +322,13 @@ class AppTestClient(object):
defer.returnValue(mails)
@defer.inlineCallbacks
- def get_attachment(self, ident, encoding):
- deferred_result, req = self.get("/attachment/%s" % ident, {'encoding': [encoding]}, as_json=False)
+ def get_attachment(self, ident, encoding, filename=None, content_type=None):
+ params = {'encoding': [encoding]}
+ if filename:
+ params['filename'] = [filename]
+ if content_type:
+ params['content_type'] = [content_type]
+ deferred_result, req = self.get("/attachment/%s" % ident, params, as_json=False)
res = yield deferred_result
defer.returnValue((res, req))
diff --git a/service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py b/service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py
index 5644ab6a..cfc9353d 100644
--- a/service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py
+++ b/service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py
@@ -21,6 +21,7 @@ from tempfile import NamedTemporaryFile
from httmock import all_requests, HTTMock, urlmatch
CERTIFICATE_DATA = 'some cert data'
+USERNAME = 'some_user_name'
@all_requests
@@ -29,12 +30,17 @@ def not_found_mock(url, request):
'content': 'foobar'}
-@urlmatch(netloc='api.some-server.test:4430', path='/1/cert')
-def ca_cert_mock(url, request):
- return {
- "status_code": 200,
- "content": CERTIFICATE_DATA
- }
+@urlmatch(netloc='api.some-server.test:4430', path='/1/smtp_cert', method='POST')
+def smtp_cert_mock(url, request):
+ if request.body == 'address=%s' % USERNAME:
+ return {
+ "status_code": 200,
+ "content": CERTIFICATE_DATA
+ }
+ else:
+ return {
+ 'status_code': 401
+ }
class TestSmtpCertDownloader(unittest.TestCase):
@@ -50,6 +56,7 @@ class TestSmtpCertDownloader(unittest.TestCase):
self._provider.api_version = '1'
self._provider.server_name = 'some.host.tld'
+ self._auth.username = USERNAME
self._auth.session_id = 'some session id'
self._auth.token = 'some token'
@@ -57,7 +64,7 @@ class TestSmtpCertDownloader(unittest.TestCase):
unstub()
def test_download_certificate(self):
- with HTTMock(ca_cert_mock, not_found_mock):
+ with HTTMock(smtp_cert_mock, not_found_mock):
cert_data = SmtpCertDownloader(self._provider, self._auth).download()
self.assertEqual(CERTIFICATE_DATA, cert_data)
@@ -71,7 +78,7 @@ class TestSmtpCertDownloader(unittest.TestCase):
downloader = SmtpCertDownloader(self._provider, self._auth)
with NamedTemporaryFile() as tmp_file:
- with HTTMock(ca_cert_mock, not_found_mock):
+ with HTTMock(smtp_cert_mock, not_found_mock):
downloader.download_to(tmp_file.name)
file_content = open(tmp_file.name).read()
diff --git a/service/test/unit/bitmask_libraries/test_smtp_client_certificate.py b/service/test/unit/bitmask_libraries/test_smtp_client_certificate.py
new file mode 100644
index 00000000..155f46e9
--- /dev/null
+++ b/service/test/unit/bitmask_libraries/test_smtp_client_certificate.py
@@ -0,0 +1,66 @@
+#
+# Copyright (c) 2016 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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 unittest
+import tempdir
+from pixelated.bitmask_libraries import session
+from leap.srp_session import SRPSession
+import leap.common.certs as certs
+from mockito import mock, unstub, when, verify, never, any as ANY
+
+from pixelated.bitmask_libraries.session import SmtpClientCertificate
+
+
+class TestSmtpClientCertificate(unittest.TestCase):
+
+ def setUp(self):
+ self.tmp_dir = tempdir.TempDir()
+ self.provider = mock()
+ self.provider.domain = 'some-provider.tld'
+ self.auth = SRPSession('username', 'token', 'uuid', 'session_id')
+ self.pem_path = os.path.join(self.tmp_dir.name, 'providers', 'some-provider.tld', 'keys', 'client', 'smtp.pem')
+ self.downloader = mock()
+ when(session).SmtpCertDownloader(self.provider, self.auth).thenReturn(self.downloader)
+
+ def tearDown(self):
+ self.tmp_dir.dissolve()
+ unstub()
+
+ def test_download_certificate(self):
+ cert = SmtpClientCertificate(self.provider, self.auth, self.tmp_dir.name)
+ result = cert.cert_path()
+
+ self.assertEqual(self.pem_path, result)
+ verify(self.downloader).download_to(self.pem_path)
+
+ def test_download_certificate_if_redownload_necessary(self):
+ when(os.path).exists(self.pem_path).thenReturn(True)
+ when(certs).should_redownload(self.pem_path).thenReturn(True)
+
+ cert = SmtpClientCertificate(self.provider, self.auth, self.tmp_dir.name)
+ result = cert.cert_path()
+
+ self.assertEqual(self.pem_path, result)
+ verify(self.downloader).download_to(self.pem_path)
+
+ def test_skip_download_if_already_downloaded_and_still_valid(self):
+ when(os.path).exists(self.pem_path).thenReturn(True)
+ when(certs).should_redownload(ANY()).thenReturn(False)
+ cert = SmtpClientCertificate(self.provider, self.auth, self.tmp_dir.name)
+ result = cert.cert_path()
+
+ self.assertEqual(self.pem_path, result)
+ verify(self.downloader, never).download_to(ANY())
diff --git a/service/test/unit/resources/test_login_resource.py b/service/test/unit/resources/test_login_resource.py
index bc238ae8..3cd9d3b2 100644
--- a/service/test/unit/resources/test_login_resource.py
+++ b/service/test/unit/resources/test_login_resource.py
@@ -1,3 +1,5 @@
+import os
+
import test.support.mockito
from leap.exceptions import SRPAuthenticationError
@@ -58,16 +60,45 @@ class TestLoginResource(unittest.TestCase):
input_username = 'name="username"'
input_password = 'name="password"'
input_submit = 'name="login"'
+ default_disclaimer = 'Some disclaimer'
written_response = ''.join(request.written)
self.assertIn(form_action, written_response)
self.assertIn(form_method, written_response)
self.assertIn(input_password, written_response)
self.assertIn(input_submit, written_response)
self.assertIn(input_username, written_response)
+ self.assertIn(default_disclaimer, written_response)
d.addCallback(assert_form_rendered)
return d
+ def _write(self, filename, content):
+ with open(filename, 'w') as disclaimer_file:
+ disclaimer_file.write(content)
+
+ def test_override_login_disclaimer_message(self):
+ request = DummyRequest([''])
+
+ banner_file_name = 'banner.txt'
+ banner_disclaimer_content = '<p>some custom disclaimer</p>'
+ self._write(banner_file_name, banner_disclaimer_content)
+
+ self.resource._disclaimer_banner = 'service/_trial_temp/' + banner_file_name
+
+ d = self.web.get(request)
+
+ def assert_custom_disclaimer_rendered(_):
+ self.assertEqual(200, request.responseCode)
+ written_response = ''.join(request.written)
+ self.assertIn(banner_disclaimer_content, written_response)
+
+ def tear_down(_):
+ os.remove(banner_file_name)
+
+ d.addCallback(assert_custom_disclaimer_rendered)
+ d.addCallback(tear_down)
+ return d
+
class TestLoginPOST(unittest.TestCase):
def setUp(self):
diff --git a/web-ui/app/templates/mails/full_view.hbs b/web-ui/app/templates/mails/full_view.hbs
index cf797ce6..f9ec084a 100644
--- a/web-ui/app/templates/mails/full_view.hbs
+++ b/web-ui/app/templates/mails/full_view.hbs
@@ -73,7 +73,7 @@
<ul>
{{#each attachments }}
<li>
- <a href="/attachment/{{ this.ident }}?encoding={{ this.encoding }}&filename={{ this.name }}">{{ this.name }} ({{ formatSize this.size}})<i class="fa fa-arrow-down download-icon"></i></a>
+ <a href="/attachment/{{ this.ident }}?content_type={{ this.content-type }}&encoding={{ this.encoding }}&filename={{ this.name }}">{{ this.name }} ({{ formatSize this.size}})<i class="fa fa-arrow-down download-icon"></i></a>
</li>
{{/each }}
</ul>
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 fe763919..1c1446f9 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
@@ -271,7 +271,7 @@ describeComponent('mail_view/ui/mail_view', function () {
this.component.displayMail({}, withAttachments);
var attachmentLink = $(this.component.$node.find('.attachmentsArea li').html());
- var expectedLink = '/attachment/912ec803b2ce49e4a541068d495ab570?encoding=base64&filename=filename.txt';
+ var expectedLink = '/attachment/912ec803b2ce49e4a541068d495ab570?content_type=text/plain&encoding=base64&filename=filename.txt';
expect(attachmentLink.attr('href')) .toBe(expectedLink);
});
diff --git a/web-ui/test/test_data.js b/web-ui/test/test_data.js
index e9388c2f..94fd74d8 100644
--- a/web-ui/test/test_data.js
+++ b/web-ui/test/test_data.js
@@ -224,7 +224,8 @@ define(function() {
attachments: [{
ident: '912ec803b2ce49e4a541068d495ab570',
name: 'filename.txt',
- encoding: 'base64'
+ encoding: 'base64',
+ 'content-type': 'text/plain'
}]
};