From 26b92825384e5836a407758fa4b6b14d8726bb9b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 4 Nov 2015 11:36:57 -0300 Subject: [bug] fix typo on signal name - Resolves: #7568 --- src/leap/bitmask/services/mail/conductor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 68197d9d..b79ea9c7 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -113,7 +113,7 @@ class IMAPControl(object): """ Callback for IMAP failed state. """ - self.imap_connection.qtsigs.connetion_aborted_signal.emit() + self.imap_connection.qtsigs.connection_aborted_signal.emit() class SMTPControl(object): -- cgit v1.2.3 From 0d662c174ff0b8c09213b9ecf8153dd283ccb91a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 4 Nov 2015 15:00:11 -0300 Subject: [feat] improve msg and add margin above box - Related: #7552 --- src/leap/bitmask/gui/ui/mail_status.ui | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui index 8e8f1848..3f103ef6 100644 --- a/src/leap/bitmask/gui/ui/mail_status.ui +++ b/src/leap/bitmask/gui/ui/mail_status.ui @@ -77,7 +77,8 @@ background-color: #e0efd8; -padding: 10px; +padding: 10px; +margin-top:5px; QFrame::NoFrame @@ -89,7 +90,7 @@ padding: 10px; 0 - Congratulations! You are ready to use Bitmask to encrypt your email. Go to <a href="https://bitmask.net/en/help/email">https://bitmask.net/en/help/email</a> for instructions on how to set up your mail client. + Bitmask is ready to encrypt your email. Go to <a href="https://bitmask.net/en/help/email">https://bitmask.net/en/help/email</a> for email application setup instructions. Qt::AutoText -- cgit v1.2.3 From e086f958dcaf417816025af6f61e6283a3bf7ebf Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Nov 2015 16:50:12 -0400 Subject: [bug] fix set_soledad_invalid_auth_token event cb signature --- src/leap/bitmask/gui/mail_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 8b4329d7..f7957c95 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -186,7 +186,7 @@ class MailStatusWidget(QtGui.QWidget): msg = self.tr("There was an unexpected problem with Soledad.") self._set_mail_status(msg, ready=-1) - def set_soledad_invalid_auth_token(self, event, content): + def set_soledad_invalid_auth_token(self, event, content=None): """ This method is called when the auth token is invalid -- cgit v1.2.3 From b370dfa37265ec7df99c8a85f2b7b7866a9a6b74 Mon Sep 17 00:00:00 2001 From: kwadronaut Date: Sat, 7 Nov 2015 15:01:11 +0100 Subject: Debian capitalization Solves https://www.transifex.com/otf/bitmask/translate/#es/$/31977728 --- src/leap/bitmask/platform_init/initializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index eb892cce..82a229ae 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -126,7 +126,7 @@ def check_missing(): if alert_missing and not flags.STANDALONE: # We refuse to install missing stuff if not running with standalone # flag. Right now we rely on the flag alone, but we can disable this - # by overwriting some constant from within the debian package. + # by overwriting some constant from within the Debian package. alert_missing = False complain_missing = True -- cgit v1.2.3 From 93d9469194ca567d14660572c65996b6330088b2 Mon Sep 17 00:00:00 2001 From: kwadronaut Date: Sat, 7 Nov 2015 17:08:45 +0100 Subject: Change title 'Missing Bitmask helpers' solves https://www.transifex.com/otf/bitmask/translate/#ar/$/31977726 --- src/leap/bitmask/platform_init/initializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 82a229ae..4e8bbdbb 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -465,7 +465,7 @@ def _get_missing_complain_dialog(stuff): self.setLayout(mainLayout) msg = ComplainDialog() - msg.setWindowTitle(msg.tr("Missing Bitmask helpers")) + msg.setWindowTitle(msg.tr("Missing Bitmask helper files")) return msg -- cgit v1.2.3 From 077a36d20f697865cb334dd809ad9bd201adcb10 Mon Sep 17 00:00:00 2001 From: kwadronaut Date: Sat, 7 Nov 2015 17:47:27 +0100 Subject: solved transifex issues, language https://www.transifex.com/otf/bitmask/translate/#ar/$/31977726 https://www.transifex.com/otf/bitmask/translate/#nl/$/32117870 https://www.transifex.com/otf/bitmask/translate/#ar/$/22113277 https://www.transifex.com/otf/bitmask/translate/#nl/$/22113279 --- src/leap/bitmask/gui/eip_status.py | 2 +- src/leap/bitmask/gui/logwindow.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 64a408c4..470ef88a 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -733,7 +733,7 @@ class EIPStatusWidget(QtGui.QWidget): self.set_eip_status( # XXX this should change to polkit-kde where # applicable. - self.tr("We could not find any authentication agent in your " + self.tr("We could not find any authentication agent on your " "system.
Make sure you have " "polkit-gnome-authentication-agent-1 running and " "try again."), diff --git a/src/leap/bitmask/gui/logwindow.py b/src/leap/bitmask/gui/logwindow.py index 718269c9..5d8c99fc 100644 --- a/src/leap/bitmask/gui/logwindow.py +++ b/src/leap/bitmask/gui/logwindow.py @@ -173,7 +173,7 @@ class LoggerWindow(QtGui.QDialog): :type sending: bool """ if sending: - self.ui.btnPastebin.setText(self.tr("Sending to pastebin...")) + self.ui.btnPastebin.setText(self.tr("Sending to Pastebin.com…")) self.ui.btnPastebin.setEnabled(False) else: self.ui.btnPastebin.setText(self.tr("Send to Pastebin.com")) @@ -193,7 +193,7 @@ class LoggerWindow(QtGui.QDialog): # We save the dialog in an instance member to avoid dialog being # deleted right after we exit this method self._msgBox = msgBox = QtGui.QMessageBox( - QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg) + QtGui.QMessageBox.Information, self.tr("Pastebin is OK"), msg) msgBox.setWindowModality(QtCore.Qt.NonModal) msgBox.show() -- cgit v1.2.3 From fca2246011b8c2402977fde93291d46a9a3438fb Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 9 Nov 2015 13:02:45 -0300 Subject: [bug] open email help link on browser - Resolves: #7585 --- src/leap/bitmask/gui/ui/mail_status.ui | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui index 3f103ef6..89e1843f 100644 --- a/src/leap/bitmask/gui/ui/mail_status.ui +++ b/src/leap/bitmask/gui/ui/mail_status.ui @@ -110,6 +110,9 @@ margin-top:5px; -1 + + true + -- cgit v1.2.3 From f4b0c01b6737e4266f12276e6b2a058bf3c0f743 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 11 Nov 2015 19:42:09 +0100 Subject: [but] Fix errback on InvalidAuthToken - Related: #7583 --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 60a2130b..a10a2731 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -654,7 +654,7 @@ class Syncer(object): logger.error('Invalid auth token while trying to sync Soledad') self._signaler.signal( self._signaler.soledad_invalid_auth_token) - self._callback_deferred.fail(failure) + self._callback_deferred.errback(failure) elif failure.check(sqlite_ProgrammingError, sqlcipher_ProgrammingError): logger.exception("%r" % (failure.value,)) -- cgit v1.2.3 From b6db2310f7595bdd6b93a2cab5680abbe5ce6599 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 10 Nov 2015 14:18:49 -0300 Subject: [i18n] do not translate https label text --- src/leap/bitmask/gui/ui/wizard.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index b125577e..37226d13 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -316,7 +316,7 @@ - https:// + https:// Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -339,7 +339,7 @@ - https:// + https:// Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -- cgit v1.2.3 From 97de43f5ef1b7fa4735e3371581bfaf4c3919de3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 30 Nov 2015 12:12:57 -0400 Subject: [docs] fix outdated signature description --- src/leap/bitmask/services/mail/imap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 5934756d..b06c1d4a 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -61,7 +61,7 @@ def start_imap_service(*args, **kwargs): """ Initializes and run imap service. - :returns: twisted.internet.task.LoopingCall instance + :returns: LeapIMAPFactory instance """ from leap.bitmask.config import flags logger.debug('Launching imap service') -- cgit v1.2.3 From 8d3309701af51df3046aa0df10545dfa35a64ef7 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 30 Nov 2015 12:15:09 -0400 Subject: [feat] multi-user events. Adapt to users emitting the userid/uuid too. - Resolves: #7656 - Releases: 0.10.0 --- src/leap/bitmask/gui/mail_status.py | 11 ++++++++--- src/leap/bitmask/services/mail/conductor.py | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index f7957c95..419a85c0 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -233,12 +233,15 @@ class MailStatusWidget(QtGui.QWidget): self._action_mail_status.setText(tray_status) self._update_systray_tooltip() - def _mail_handle_soledad_events(self, event, content): + def _mail_handle_soledad_events(self, event, user_data, content=""): """ Callback for handling events that are emitted from Soledad :param event: The event that triggered the callback. :type event: str + :param user_id: The user_data of the soledad user. Ignored right now, + since we're only contemplating single-user in soledad. + :type user_id: dict :param content: The content of the event. :type content: dict """ @@ -346,7 +349,7 @@ class MailStatusWidget(QtGui.QWidget): logger.warning("don't know to to handle %s" % (event,)) self._set_mail_status(ext_status, ready=1) - def _mail_handle_smtp_events(self, event): + def _mail_handle_smtp_events(self, event, content=""): """ Callback for the SMTP events @@ -380,12 +383,14 @@ class MailStatusWidget(QtGui.QWidget): # ----- XXX deprecate (move to mail conductor) - def _mail_handle_imap_events(self, event, content): + def _mail_handle_imap_events(self, event, uuid, content=""): """ Callback for the IMAP events :param event: The event that triggered the callback. :type event: str + :param uuid: The UUID for the user. Ignored right now. + :type uuid: str :param content: The content of the event. :type content: list """ diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index b79ea9c7..738531e6 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -73,12 +73,13 @@ class IMAPControl(object): self._backend.imap_stop_service() - def _handle_imap_events(self, event, content): + def _handle_imap_events(self, event, userid=None, content=None): """ Callback handler for the IMAP events :param event: The event that triggered the callback. :type event: str + :param userid: The user id of the logged in user. Ignored. :param content: The content of the event. :type content: list """ -- cgit v1.2.3 From 6c0299e7e2df65651ff8738fa18acbc08af18c32 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 30 Nov 2015 16:31:10 -0400 Subject: [style] autopep8 --- src/leap/bitmask/services/mail/conductor.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 738531e6..b94b3cc8 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -34,6 +34,7 @@ class IMAPControl(object): """ Methods related to IMAP control. """ + def __init__(self): """ Initializes smtp variables. @@ -118,6 +119,7 @@ class IMAPControl(object): class SMTPControl(object): + def __init__(self): """ Initializes smtp variables. -- cgit v1.2.3 From 98384361a7c49ad4e0ff0127fd923a8b72cc910a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 8 Dec 2015 21:04:11 -0400 Subject: [feat] adapt to use cred-based authentication for imap This includes getting the token for the imap authentication, and displaying it on the help window. - Resolves: #4469 - Releases: 0.10.0 --- src/leap/bitmask/backend/api.py | 2 ++ src/leap/bitmask/backend/components.py | 23 ++++++++++++++++++ src/leap/bitmask/backend/leapbackend.py | 7 ++++++ src/leap/bitmask/backend/leapsignaler.py | 1 + src/leap/bitmask/gui/mainwindow.py | 18 +++++++++++++- src/leap/bitmask/services/mail/imap.py | 31 ++++++++++++------------ src/leap/bitmask/services/mail/imapcontroller.py | 8 +++--- 7 files changed, 68 insertions(+), 22 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py index 48aa2090..134a2d56 100644 --- a/src/leap/bitmask/backend/api.py +++ b/src/leap/bitmask/backend/api.py @@ -57,6 +57,7 @@ API = ( "soledad_change_password", "soledad_close", "soledad_load_offline", + "soledad_get_service_token", "tear_fw_down", "bitmask_root_vpn_down", "user_cancel_login", @@ -135,6 +136,7 @@ SIGNALS = ( "soledad_offline_finished", "soledad_password_change_error", "soledad_password_change_ok", + "soledad_got_service_token", "srp_auth_bad_user_or_password", "srp_auth_connection_error", "srp_auth_error", diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 5f34d290..a07d3bad 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -763,6 +763,7 @@ class Soledad(object): self._signaler = signaler self._soledad_bootstrapper = SoledadBootstrapper(signaler) self._soledad_defer = None + self._service_tokens = {} def bootstrap(self, username, domain, password): """ @@ -786,6 +787,7 @@ class Soledad(object): provider_config, username, password, download_if_needed=True) self._soledad_defer.addCallback(self._set_proxies_cb) + self._soledad_defer.addCallback(self._set_service_tokens_cb) else: if self._signaler is not None: self._signaler.signal(self._signaler.soledad_bootstrap_failed) @@ -793,6 +795,21 @@ class Soledad(object): return self._soledad_defer + def _set_service_tokens_cb(self, result): + + def register_imap_token(imap_token): + self._service_tokens['imap'] = imap_token + if self._signaler is not None: + self._signaler.signal( + self._signaler.soledad_got_service_token, + ('imap', imap_token)) + + sol = self._soledad_bootstrapper.soledad + d = sol.get_or_create_service_token('imap') + d.addCallback(register_imap_token) + d.addCallback(lambda _: result) + return d + def _set_proxies_cb(self, _): """ Update the soledad and keymanager proxies to reference the ones created @@ -803,6 +820,12 @@ class Soledad(object): zope.proxy.setProxiedObject(self._keymanager_proxy, self._soledad_bootstrapper.keymanager) + def get_service_token(self, service): + """ + Get an authentication token for a given service. + """ + return self._service_tokens.get(service, '') + def load_offline(self, username, password, uuid): """ Load the soledad database in offline mode. diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index cf45c4f8..d0668a1c 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -35,6 +35,7 @@ class LeapBackend(Backend): """ Backend server subclass, used to implement the API methods. """ + def __init__(self, bypass_checks=False, frontend_pid=None): """ Constructor for the backend. @@ -438,6 +439,12 @@ class LeapBackend(Backend): """ self._soledad.load_offline(username, password, uuid) + def soledad_get_service_token(self, service): + """ + Attempt to get an authentication token for a given service. + """ + self._soledad.get_service_token(service) + def soledad_cancel_bootstrap(self): """ Cancel the ongoing soledad bootstrapping process (if any). diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py index 1ac51f5e..13a9fa5f 100644 --- a/src/leap/bitmask/backend/leapsignaler.py +++ b/src/leap/bitmask/backend/leapsignaler.py @@ -97,6 +97,7 @@ class LeapSignaler(SignalerQt): soledad_offline_finished = QtCore.Signal() soledad_password_change_error = QtCore.Signal() soledad_password_change_ok = QtCore.Signal() + soledad_got_service_token = QtCore.Signal(object) srp_auth_bad_user_or_password = QtCore.Signal() srp_auth_connection_error = QtCore.Signal() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a8a4e41d..189a6295 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -407,6 +407,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): sig.soledad_invalid_auth_token.connect( self._mail_status.set_soledad_invalid_auth_token) + self._service_tokens = {} + sig.soledad_got_service_token.connect( + self._set_service_tokens) + # TODO: connect this with something # sig.soledad_cancelled_bootstrap.connect() @@ -1033,6 +1037,13 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10], greet=greet) QtGui.QMessageBox.about(self, title, msg) + def _set_service_tokens(self, data): + """ + Set the received service token. + """ + service, token = data + self._service_tokens[service] = token + def _help(self): """ TRIGGERS: @@ -1063,7 +1074,12 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): manual_imap = self.tr("IMAP: localhost, port {0}".format(IMAP_PORT)) manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port)) manual_username = self.tr("Username: your full email address") - manual_password = self.tr("Password: any non-empty text") + + # TODO this should be a widget that allows to be copied to the + # clipboard. + imap_token = (self._service_tokens.get('imap', None) + or "??? (log in to unlock)") + manual_password = self.tr("Password: ") + "%s" % (imap_token, ) msg = help_url + self.tr( "

{0}

" diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index b06c1d4a..54935f8c 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -20,11 +20,14 @@ Initialization of imap service import os import sys +from twisted.python import log + from leap.bitmask.logs.utils import get_logger from leap.mail.constants import INBOX_NAME from leap.mail.imap.service import imap from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD -from twisted.python import log +from leap.mail.mail import Account + logger = get_logger() @@ -57,11 +60,13 @@ def get_mail_check_period(): return period -def start_imap_service(*args, **kwargs): +def start_imap_service(soledad_sessions): """ Initializes and run imap service. - :returns: LeapIMAPFactory instance + :returns: the port as returned by the reactor when starts listening, and + the factory for the protocol. + :rtype: tuple """ from leap.bitmask.config import flags logger.debug('Launching imap service') @@ -70,10 +75,10 @@ def start_imap_service(*args, **kwargs): log.startLogging(open(flags.MAIL_LOGFILE, 'w')) log.startLogging(sys.stdout) - return imap.run_service(*args, **kwargs) + return imap.run_service(soledad_sessions) -def start_incoming_mail_service(keymanager, soledad, imap_factory, userid): +def start_incoming_mail_service(keymanager, soledad, userid): """ Initalizes and starts the incomming mail service. @@ -81,19 +86,13 @@ def start_incoming_mail_service(keymanager, soledad, imap_factory, userid): """ def setUpIncomingMail(inbox): incoming_mail = IncomingMail( - keymanager, - soledad, - inbox.collection, - userid, + keymanager, soledad, + inbox, userid, check_period=get_mail_check_period()) return incoming_mail - # XXX: do I really need to know here how to get a mailbox?? - # XXX: ideally, the parent service in mail would take care of initializing - # the account, and passing the mailbox to the incoming service. - # In an even better world, we just would subscribe to a channel that would - # pass us the serialized object to be inserted. - acc = imap_factory.theAccount - d = acc.callWhenReady(lambda _: acc.getMailbox(INBOX_NAME)) + acc = Account(soledad) + d = acc.callWhenReady(lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) + d.addErrback(log.err) return d diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index 5053d897..855fb74b 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -60,9 +60,9 @@ class IMAPController(object): """ logger.debug('Starting imap service') + soledad_sessions = {userid: self._soledad} self.imap_port, self.imap_factory = imap.start_imap_service( - self._soledad, - userid=userid) + soledad_sessions) def start_and_assign_incoming_service(incoming_mail): # this returns a deferred that will be called when the looping call @@ -74,9 +74,7 @@ class IMAPController(object): if offline is False: d = imap.start_incoming_mail_service( - self._keymanager, - self._soledad, - self.imap_factory, + self._keymanager, self._soledad, userid) d.addCallback(start_and_assign_incoming_service) d.addErrback(lambda f: logger.error(f.printTraceback())) -- cgit v1.2.3 From 7b80dd1fca9828331f3327c418913539a3a303c0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 16 Dec 2015 15:33:25 -0400 Subject: [feat] adapt to use cred-based authentication for smtp --- src/leap/bitmask/backend/components.py | 14 +++++---- src/leap/bitmask/gui/mainwindow.py | 16 +++++++---- src/leap/bitmask/services/mail/smtpbootstrapper.py | 33 +++++++++++++--------- 3 files changed, 38 insertions(+), 25 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index a07d3bad..e93ca19f 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -797,16 +797,19 @@ class Soledad(object): def _set_service_tokens_cb(self, result): - def register_imap_token(imap_token): - self._service_tokens['imap'] = imap_token + def register_service_token(token, service): + self._service_tokens[service] = token if self._signaler is not None: self._signaler.signal( self._signaler.soledad_got_service_token, - ('imap', imap_token)) + (service, token)) sol = self._soledad_bootstrapper.soledad d = sol.get_or_create_service_token('imap') - d.addCallback(register_imap_token) + d.addCallback(register_service_token, 'imap') + d.addCallback( + lambda _: sol.get_or_create_service_token('smtp')) + d.addCallback(register_service_token, 'smtp') d.addCallback(lambda _: result) return d @@ -1035,7 +1038,8 @@ class Mail(object): """ return threads.deferToThread( self._smtp_bootstrapper.start_smtp_service, - self._keymanager_proxy, full_user_id, download_if_needed) + self._soledad_proxy, self._keymanager_proxy, full_user_id, + download_if_needed) def start_imap_service(self, full_user_id, offline=False): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 189a6295..759b454f 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1075,11 +1075,14 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port)) manual_username = self.tr("Username: your full email address") - # TODO this should be a widget that allows to be copied to the - # clipboard. - imap_token = (self._service_tokens.get('imap', None) - or "??? (log in to unlock)") - manual_password = self.tr("Password: ") + "%s" % (imap_token, ) + # FIXME on i3, this doens't allow to mouse-select. + # Switch to a dialog in which we can set the QLabel + imap_token = (self._service_tokens.get('imap', None) or + "??? (log in to unlock)") + smtp_token = (self._service_tokens.get('smtp', None) or + "??? (log in to unlock)") + imap_password = self.tr("IMAP Password:") + " %s" % (imap_token,) + smtp_password = self.tr("SMTP Password:") + " %s" % (smtp_token,) msg = help_url + self.tr( "

{0}

" @@ -1090,9 +1093,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): "
  •  {4}
  • " "
  •  {5}
  • " "
  •  {6}
  • " + "
  •  {7}
  • " "

    ").format(email_quick_reference, thunderbird_text, manual_text, manual_imap, manual_smtp, - manual_username, manual_password) + manual_username, imap_password, smtp_password) QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) def _needs_update(self): diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index a577509e..dadf59dd 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -19,6 +19,7 @@ SMTP bootstrapping """ import os import warnings +from collections import namedtuple from requests.exceptions import HTTPError @@ -127,9 +128,6 @@ class SMTPBootstrapper(AbstractBootstrapper): Start the smtp service using the downloaded configurations. """ # TODO Make the encrypted_only configurable - # TODO pick local smtp port in a better way - # TODO remove hard-coded port and let leap.mail set - # the specific default. # TODO handle more than one host and define how to choose hosts = self._smtp_config.get_hosts() hostname = hosts.keys()[0] @@ -138,19 +136,25 @@ class SMTPBootstrapper(AbstractBootstrapper): client_cert_path = self._smtp_config.get_client_cert_path( self._userid, self._provider_config, about_to_download=True) - from leap.mail.smtp import setup_smtp_gateway + # XXX this should be defined in leap.mail.smtp, it's in bitmask.core + # right now. + SendmailOpts = namedtuple( + 'SendmailOpts', ['cert', 'key', 'hostname', 'port']) - self._smtp_service, self._smtp_port = setup_smtp_gateway( - port=2013, - userid=self._userid, - keymanager=self._keymanager, - smtp_host=host, - smtp_port=port, - smtp_cert=client_cert_path, - smtp_key=client_cert_path, - encrypted_only=False) + userid = self._userid + soledad_sessions = {userid: self._soledad} + keymanager_sessions = {userid: self._keymanager} - def start_smtp_service(self, keymanager, userid, download_if_needed=False): + key = cert = client_cert_path + opts = SendmailOpts(cert, key, host, port) + sendmail_opts = {userid: opts} + + from leap.mail.smtp import run_service + self._smtp_service, self._smtp_port = run_service( + soledad_sessions, keymanager_sessions, sendmail_opts) + + def start_smtp_service(self, soledad, keymanager, userid, + download_if_needed=False): """ Starts the SMTP service. @@ -170,6 +174,7 @@ class SMTPBootstrapper(AbstractBootstrapper): raise MalformedUserId() self._provider_config = ProviderConfig.get_provider_config(domain) + self._soledad = soledad self._keymanager = keymanager self._smtp_config = SMTPConfig() self._userid = str(userid) -- cgit v1.2.3 From fa05e417d58f1cec68a78e235989f01181f9632f Mon Sep 17 00:00:00 2001 From: PaixuAabuizia Date: Sun, 24 Jan 2016 14:48:48 +0100 Subject: [bug] change backend process spawn method the original implementation causes problems in win32 (see #7240) possibly because pyinstaller cannot resolve the lambda correctly. using the documented multiprocessing.Process approach to pass parameters works here --- src/leap/bitmask/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index a1b7481a..982d8a13 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -223,9 +223,9 @@ def start_app(): backend_pid = None if not backend_running: frontend_pid = os.getpid() - backend = lambda: run_backend(opts.danger, flags_dict, frontend_pid) - backend_process = multiprocessing.Process(target=backend, - name='Backend') + backend_process = multiprocessing.Process(target=run_backend, + name='Backend', + args=(opts.danger, flags_dict, frontend_pid)) # we don't set the 'daemon mode' since we need to start child processes # in the backend # backend_process.daemon = True -- cgit v1.2.3 From 449de440ae1d3e533291ff6a2f41edd702c7746a Mon Sep 17 00:00:00 2001 From: PaixuAabuizia Date: Wed, 3 Feb 2016 11:59:52 +0100 Subject: [bug] frozen backend fails to spawn on windows according to [1] the backend should raise a Runtime Error, instead what happens is that the process is spawned again and again but never runs actual code. [1] https://docs.python.org/2.7/library/multiprocessing.html#multiprocessing.freeze_support --- src/leap/bitmask/app.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 982d8a13..dc1ee6df 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -237,4 +237,5 @@ def start_app(): if __name__ == "__main__": + multiprocessing.freeze_support() start_app() -- cgit v1.2.3 From 8e17fb43b4cbda9ee7b386d084e01fc99345f060 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Dec 2015 15:48:00 +0100 Subject: [feat] use fingerprint instead of key_id to address keys --- src/leap/bitmask/backend/components.py | 3 ++- src/leap/bitmask/gui/advanced_key_management.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index e93ca19f..d8e4edbf 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -984,7 +984,8 @@ class Keymanager(object): List all the keys stored in the local DB. """ def signal_details(public_key): - details = (public_key.key_id, public_key.fingerprint) + # XXX: We should avoid the key-id + details = (public_key.fingerprint[-16:], public_key.fingerprint) self._signaler.signal(self._signaler.keymanager_key_details, details) diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 2e315d18..bc496a57 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -94,6 +94,7 @@ class AdvancedKeyManagement(QtGui.QDialog): """ Set the current user's key details into the gui. """ + # XXX: We should avoid the key-id self.ui.leKeyID.setText(details[0]) self.ui.leFingerprint.setText(details[1]) @@ -246,7 +247,7 @@ class AdvancedKeyManagement(QtGui.QDialog): row = keys_table.rowCount() keys_table.insertRow(row) keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address)) - keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id)) + keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.fingerprint)) def _backend_connect(self): """ -- cgit v1.2.3 From 9a658c81619af892a4b29e0122cd1c1ba2e428e4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:27:55 -0400 Subject: [pkg] move to versioneer 0.15 --- src/leap/bitmask/__init__.py | 50 +--- src/leap/bitmask/_version.py | 553 +++++++++++++++++++++++++++++++------------ 2 files changed, 414 insertions(+), 189 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index 9ec5aae7..966ce91e 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -19,12 +19,6 @@ Init file for leap.bitmask Initializes version and app info. """ -import re - -from pkg_resources import parse_version - -from leap.bitmask.util import first - # HACK: This is a hack so that py2app copies _scrypt.so to the right # place, it can't be technically imported, but that doesn't matter # because the import is never executed @@ -32,7 +26,7 @@ if False: import _scrypt # noqa - skip 'not used' warning -def _is_release_version(version): +def _is_release_version(version_str): """ Helper to determine whether a version is a final release or not. The release needs to be of the form: w.x.y.z containing only numbers @@ -43,40 +37,12 @@ def _is_release_version(version): :returns: if the version is a release version or not. :rtype: bool """ - parsed_version = parse_version(version) - not_number = 0 - for x in parsed_version: - try: - int(x) - except: - not_number += 1 - - return not_number == 1 - - -__version__ = "unknown" -IS_RELEASE_VERSION = False - -__short_version__ = "unknown" - -try: - from leap.bitmask._version import get_versions - __version__ = get_versions()['version'] - __version_hash__ = get_versions()['full'] - IS_RELEASE_VERSION = _is_release_version(__version__) - del get_versions -except ImportError: - # running on a tree that has not run - # the setup.py setver - pass + parts = __version__.split('.') + patch = parts[2] + return patch.isdigit() -__appname__ = "unknown" -try: - from leap.bitmask._appname import __appname__ -except ImportError: - # running on a tree that has not run - # the setup.py setver - pass -__short_version__ = first(re.findall('\d+\.\d+\.\d+', __version__)) -__full_version__ = __appname__ + '/' + str(__version__) +from ._version import get_versions +__version__ = get_versions()['version'] +IS_RELEASE_VERSION = _is_release_version(__version__) +del get_versions diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index 412b0c9e..93700af1 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -1,201 +1,460 @@ -IN_LONG_VERSION_PY = True # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build +# feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) - -# these strings will be replaced by git during git-archive -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - +# versioneer-0.15 (https://github.com/warner/python-versioneer) +import errno +import os +import re import subprocess import sys -import re -import os.path -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError: - e = sys.exc_info()[1] +def get_keywords(): + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "$Format:%d$" + git_full = "$Format:%H$" + keywords = {"refnames": git_refnames, "full": git_full} + return keywords + + +class VersioneerConfig: + pass + + +def get_config(): + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "pep440" + cfg.tag_prefix = "" + cfg.parentdir_prefix = "None" + cfg.versionfile_source = "src/leap/bitmask/_version.py" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + pass + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + def decorate(f): + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None + else: if verbose: - print("unable to run %s" % args[0]) - print(e) + print("unable to find command, tried %s" % (commands,)) return None stdout = p.communicate()[0].strip() - if sys.version >= '3': + if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: - print("unable to run %s (error)" % args[0]) + print("unable to run %s (error)" % dispcmd) return None return stdout -def get_expanded_variables(versionfile_source): +def versions_from_parentdir(parentdir_prefix, root, verbose): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - for line in open(versionfile_source, "r").readlines(): + f = open(versionfile_abs, "r") + for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) + f.close() except EnvironmentError: pass - return variables + return keywords -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) - for ref in list(refs): - if not re.search(r'\d', ref): - if verbose: - print("discarding '%s', no digits" % ref) - refs.discard(ref) - # Assume all version tags have a digit. git's %d expansion - # behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us - # distinguish between branches and tags. By ignoring refnames - # without digits, we filter out many common branch names like - # "release" and "stabilization", as well as "HEAD" and "master". + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) if verbose: - print("remaining refs: %s" % ",".join(sorted(refs))) - for ref in sorted(refs): + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, - "full": variables["full"].strip()} - # no suitable tags, so we use the full revision id + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: - print("no suitable tags, using full revision id") - return {"version": variables["full"].strip(), - "full": variables["full"].strip()} - - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) - return {} + raise NotThisMethod("no .git directory") - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" % ( - stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope + GITS = ["git.cmd", "git.exe"] + # if there is a tag, this yields TAG-NUM-gHEX[-dirty] + # if there are no tags, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long"], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + return pieces + + +def plus_or_dot(pieces): + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + # now build up version string, with post-release "local version + # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + # exceptions: + # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + # TAG[.post.devDISTANCE] . No -dirty + + # exceptions: + # 1: no tags. 0.post.devDISTANCE + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that + # .dev0 sorts backwards (a dirty tree will appear "older" than the + # corresponding clean one), but you shouldn't be releasing software with + # -dirty anyways. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty + # --always' + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty + # --always -long'. The distance/hash is unconditional. + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +def get_versions(): + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + + try: + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + + try: + root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) - else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree"} - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' " - "doesn't start with prefix '%s'" % - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "" -parentdir_prefix = "bitmask-" -versionfile_source = "src/leap/bitmask/_version.py" - - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = {"refnames": git_refnames, "full": git_full} - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose) - if not ver: - ver = default - return ver + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version"} -- cgit v1.2.3 From 470d24ec886adedae6d47e684ab1dc14aa2c21ea Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:28:30 -0400 Subject: [style] pep8 --- src/leap/bitmask/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index 93700af1..8c507c92 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -155,7 +155,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): -- cgit v1.2.3 From 3d2df02b549a32c15b2bb369a33789abadb60c92 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:44:18 -0400 Subject: [bug] fix some version handling errors --- src/leap/bitmask/__init__.py | 6 +++++- src/leap/bitmask/provider/__init__.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index 966ce91e..6ab55e53 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -38,11 +38,15 @@ def _is_release_version(version_str): :rtype: bool """ parts = __version__.split('.') - patch = parts[2] + try: + patch = parts[2] + except IndexError: + return False return patch.isdigit() from ._version import get_versions __version__ = get_versions()['version'] +__version_hash__ = get_versions()['full-revisionid'] IS_RELEASE_VERSION = _is_release_version(__version__) del get_versions diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index 4385a92f..60a41181 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -22,7 +22,7 @@ import os from pkg_resources import parse_version -from leap.bitmask import __short_version__ as BITMASK_VERSION +from leap.bitmask import __version__ as BITMASK_VERSION from leap.common.check import leap_assert logger = logging.getLogger(__name__) -- cgit v1.2.3 From 21a0e07ffccbee57fa02f20525441f96516132dc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 21 Mar 2016 15:52:43 -0400 Subject: [bug] re-download smtp certificate if needed. the should_redownload function was only called if the pemfile was not present of the fs. - Resolves: #7869 --- src/leap/bitmask/services/mail/smtpbootstrapper.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index dadf59dd..f73687a7 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -29,7 +29,6 @@ from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.mail.smtpconfig import SMTPConfig -from leap.bitmask.util import is_file from leap.common import certs as leap_certs from leap.common.check import leap_assert @@ -93,11 +92,13 @@ class SMTPBootstrapper(AbstractBootstrapper): client_cert_path = self._smtp_config.get_client_cert_path( self._userid, self._provider_config, about_to_download=True) - if not is_file(client_cert_path): + needs_download = leap_certs.should_redownload(client_cert_path) + + if needs_download: # For re-download if something is wrong with the cert + # FIXME this doesn't read well. should reword the logic here. self._download_if_needed = ( - self._download_if_needed and - not leap_certs.should_redownload(client_cert_path)) + self._download_if_needed and not needs_download) if self._download_if_needed and os.path.isfile(client_cert_path): check_and_fix_urw_only(client_cert_path) -- cgit v1.2.3 From b596eab3591414c4ef58648d74434bc0d5d7c6e3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 11:52:44 -0400 Subject: [feature] pixelated adaptor --- src/leap/bitmask/pix.py | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/leap/bitmask/pix.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py new file mode 100644 index 00000000..abc8a822 --- /dev/null +++ b/src/leap/bitmask/pix.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# pix.py +# Copyright (C) 2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Pixelated plugin integration. +""" +import os + +from twisted.internet import defer +from twisted.python import log + +from leap.mail.imap.account import IMAPAccount + +from pixelated.adapter.mailstore import LeapMailStore +from pixelated.adapter.welcome_mail import add_welcome_mail +from pixelated.application import SingleUserServicesFactory +from pixelated.application import UserAgentMode +from pixelated.application import start_site +from pixelated.bitmask_libraries.smtp import LeapSMTPConfig +from pixelated.bitmask_libraries.session import SessionCache +from pixelated.config import services +from pixelated.resources.root_resource import RootResource + + +def start_pixelated_user_agent(userid, soledad, keymanager): + + print 'STARTING PIXELATED USER AGENT...' + + leap_session = LeapSessionAdapter( + userid, soledad, keymanager) + + config = Config() + leap_home = os.path.expanduser('~/.config/leap') + config.leap_home = leap_home + leap_session.config = config + + services_factory = SingleUserServicesFactory( + UserAgentMode(is_single_user=True)) + resource = RootResource(services_factory) + + config.port = 9090 + config.sslkey = None + config.sslcert = None + config.host = 'localhost' + + deferred = _start_in_single_user_mode( + leap_session, config, + resource, services_factory) + return deferred + + +class LeapSessionAdapter(object): + + def __init__(self, userid, soledad, keymanager): + self.userid = userid + + self.soledad = soledad + + self.nicknym = Config() + self.nicknym.keymanager = keymanager + + self.mail_store = LeapMailStore(soledad) + + self.user_auth = Config() + self.user_auth.uuid = soledad.uuid + + # XXX what is this?? path to smtp-service? + # self.config = provider.config + # self.provider = provider + + self.fresh_account = False + self.incoming_mail_fetcher = None + self.account = IMAPAccount(userid, soledad, defer.Deferred()) + + username, provider = userid.split('@') + smtp_client_cert = os.path.expanduser( + '~/.config/leap/providers/{provider}/keys/' + 'client/smtp_{username}.pem'.format( + provider=provider, username=username)) + # TODO --- get from config + smtp_host = 'antelope.mail.bitmask.net' + smtp_port = 2013 + + self.smtp_config = LeapSMTPConfig( + userid, + smtp_client_cert, smtp_host, smtp_port) + + def account_email(self): + return self.userid + + def close(self): + pass + + @property + def is_closed(self): + return self._is_closed + + def remove_from_cache(self): + key = SessionCache.session_key(self.provider, self.userid) + SessionCache.remove_session(key) + + def sync(self): + return self.soledad.sync() + + +class Config(object): + pass + + +def _start_in_single_user_mode(leap_session, config, resource, + services_factory): + start_site(config, resource) + return start_user_agent_in_single_user_mode( + resource, services_factory, + leap_session.config.leap_home, leap_session) + + +@defer.inlineCallbacks +def start_user_agent_in_single_user_mode( + root_resource, services_factory, leap_home, leap_session): + log.msg('Bootstrap done, loading services for user %s' + % leap_session.userid) + + _services = services.Services(leap_session) + yield _services.setup() + + if leap_session.fresh_account: + yield add_welcome_mail(leap_session.mail_store) + + services_factory.add_session(leap_session.user_auth.uuid, _services) + root_resource.initialize() + log.msg('Done, the user agent is ready to be used') -- cgit v1.2.3 From c866892b988912a5b9a88edfd6d1e71491617822 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 11:53:15 -0400 Subject: [feature] quick integration with legacy gui --- src/leap/bitmask/backend/api.py | 2 ++ src/leap/bitmask/backend/components.py | 13 ++++++++++++- src/leap/bitmask/backend/leapbackend.py | 6 ++++++ src/leap/bitmask/services/mail/conductor.py | 15 ++++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py index 134a2d56..2fd983ae 100644 --- a/src/leap/bitmask/backend/api.py +++ b/src/leap/bitmask/backend/api.py @@ -42,6 +42,8 @@ API = ( "keymanager_export_keys", "keymanager_get_key_details", "keymanager_list_keys", + "pixelated_start_service", + "pixelated_stop_service", "provider_bootstrap", "provider_cancel_setup", "provider_get_all_services", diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index d8e4edbf..0f8864c3 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -26,7 +26,7 @@ import time from functools import partial -from twisted.internet import threads, defer +from twisted.internet import threads, defer, reactor from twisted.python import log import zope.interface @@ -38,6 +38,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_LINUX +from leap.bitmask.pix import start_pixelated_user_agent from leap.bitmask.provider.pinned import PinnedProviders from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services import get_supported @@ -1086,6 +1087,16 @@ class Mail(object): """ return threads.deferToThread(self._stop_imap_service) + def start_pixelated_service(self, full_user_id): + reactor.callFromThread( + start_pixelated_user_agent, + full_user_id, + self._soledad_proxy, + self._keymanager_proxy) + + def stop_pixelated_service(self): + pass + class Authenticate(object): """ diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index d0668a1c..56b1597c 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -531,6 +531,12 @@ class LeapBackend(Backend): """ self._mail.stop_imap_service() + def pixelated_start_service(self, full_user_id): + self._mail.start_pixelated_service(full_user_id) + + def pixelated_stop_service(self): + self._mail.stop_pixelated_service() + def settings_set_selected_gateway(self, provider, gateway): """ Set the selected gateway for a given provider. diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index b94b3cc8..05fafa1a 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -192,7 +192,17 @@ class SMTPControl(object): self.smtp_connection.qtsigs.connection_aborted_signal.emit() -class MailConductor(IMAPControl, SMTPControl): +class PixelatedControl(object): + + def start_pixelated_service(self): + self._backend.pixelated_start_service( + full_user_id=self.userid) + + def stop_pixelated_service(self): + pass + + +class MailConductor(IMAPControl, SMTPControl, PixelatedControl): """ This class encapsulates everything related to the initialization and process control for the mail services. @@ -269,6 +279,9 @@ class MailConductor(IMAPControl, SMTPControl): self.start_smtp_service(download_if_needed=download_if_needed) self.start_imap_service() + # TODO --- check if it's enabled!!! + self.start_pixelated_service() + self._mail_services_started = True def stop_mail_services(self): -- cgit v1.2.3 From e92e4f1e4d57957af5d8c9e08a6c3c9152409612 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 12:03:46 -0400 Subject: [feature] allow to disable pixelmail integration --- src/leap/bitmask/config/leapsettings.py | 11 ++++++++++- src/leap/bitmask/services/mail/conductor.py | 7 +++++-- 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 484a8a25..a060c8a4 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -70,6 +70,7 @@ class LeapSettings(object): PINNED_KEY = "Pinned" SKIPFIRSTRUN_KEY = "SkipFirstRun" UUIDFORUSER_KEY = "%s/%s_uuid" + PIXELMAIL_KEY = "Pixmail" # values GATEWAY_AUTOMATIC = "Automatic" @@ -352,4 +353,12 @@ class LeapSettings(object): :type skip: bool """ leap_assert_type(skip, bool) - self._settings.setValue(self.SKIPFIRSTRUN_KEY, skip) + self._settings.setvalue(self.skipfirstrun_key, skip) + + def get_pixelmail_enabled(self): + return to_bool(self._settings.value(self.PIXELMAIL_KEY, False)) + + def set_pixelmail_enabled(self, enabled): + leap_assert_type(enabled, bool) + self._settings.setvalue(self.PIXELMAIL_KEY, enabled) + diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 05fafa1a..cccbcf14 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -18,6 +18,7 @@ Mail Services Conductor """ from leap.bitmask.config import flags +from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui import statemachines from leap.bitmask.services.mail import connection as mail_connection @@ -279,8 +280,10 @@ class MailConductor(IMAPControl, SMTPControl, PixelatedControl): self.start_smtp_service(download_if_needed=download_if_needed) self.start_imap_service() - # TODO --- check if it's enabled!!! - self.start_pixelated_service() + settings = LeapSettings() + pixelmail = settings.get_pixelmail_enabled() + if pixelmail: + self.start_pixelated_service() self._mail_services_started = True -- cgit v1.2.3 From f9cb960dea642ec2e9cced1ab4712577cc0f3469 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 23 Mar 2016 19:30:54 -0400 Subject: [feature] add QtWebView to render pixelated mua --- src/leap/bitmask/gui/mainwindow.py | 8 ++++++++ src/leap/bitmask/gui/qt_browser.py | 13 +++++++++++++ src/leap/bitmask/gui/ui/mainwindow.ui | 6 ++++++ 3 files changed, 27 insertions(+) create mode 100644 src/leap/bitmask/gui/qt_browser.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 759b454f..1f497d2d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -60,6 +60,8 @@ from leap.bitmask.util.keyring_helpers import has_keyring from leap.common.events import register from leap.common.events import catalog +from .qt_browser import PixelatedWindow + from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow @@ -218,6 +220,8 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) + self.ui.action_pixelated_mail.triggered.connect( + self._show_pixelated_browser) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._show_wizard) @@ -568,6 +572,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): pref_win = PreferencesWindow(self, account, self.app) pref_win.show() + def _show_pixelated_browser(self): + win = PixelatedWindow(self) + win.show() + def _update_eip_enabled_status(self, account=None, services=None): """ TRIGGER: diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py new file mode 100644 index 00000000..4bd947cb --- /dev/null +++ b/src/leap/bitmask/gui/qt_browser.py @@ -0,0 +1,13 @@ +from PySide import QtCore, QtWebKit, QtGui + +PIXELATED_URI = 'http://localhost:9090' + + +class PixelatedWindow(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QDialog.__init__(self, parent) + self.web = QtWebKit.QWebView(self) + self.web.load(QtCore.QUrl(PIXELATED_URI)) + self.setWindowTitle('Bitmask/Pixelated WebMail') + self.web.show() diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index b1d68c4a..976c0c0a 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -315,6 +315,7 @@
    + @@ -370,6 +371,11 @@ Create a new account...
    + + + Bitmask Webmail + + false -- cgit v1.2.3 From 106d202012f8f052a3cabe044d7287d2283655fc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Mar 2016 10:04:38 -0400 Subject: [feature] hide browser menu entry if pixelated is disabled --- src/leap/bitmask/gui/mainwindow.py | 4 ++++ src/leap/bitmask/gui/qt_browser.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 1f497d2d..839aae87 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -222,6 +222,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self.ui.action_preferences.triggered.connect(self._show_preferences) self.ui.action_pixelated_mail.triggered.connect( self._show_pixelated_browser) + + pixelated_enabled = self._settings.get_pixelmail_enabled() + self.ui.action_pixelated_mail.setVisible(pixelated_enabled) + self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._show_wizard) diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index 4bd947cb..e480b363 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -1,3 +1,23 @@ +# -*- coding: utf-8 -*- +# qt_browser.py +# Copyright (C) 2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +QtWebKit-based browser to display Pixelated User Agent +""" + from PySide import QtCore, QtWebKit, QtGui PIXELATED_URI = 'http://localhost:9090' -- cgit v1.2.3 From 8ab91a80f1c0ef4e49f682342e9479d140f55c9a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Mar 2016 10:50:53 -0400 Subject: [fix] get smtp config from smtp-provider.json --- src/leap/bitmask/pix.py | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index abc8a822..96ca4299 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -17,11 +17,13 @@ """ Pixelated plugin integration. """ +import json import os from twisted.internet import defer from twisted.python import log +from leap.bitmask.util import get_path_prefix from leap.mail.imap.account import IMAPAccount from pixelated.adapter.mailstore import LeapMailStore @@ -37,13 +39,11 @@ from pixelated.resources.root_resource import RootResource def start_pixelated_user_agent(userid, soledad, keymanager): - print 'STARTING PIXELATED USER AGENT...' - leap_session = LeapSessionAdapter( userid, soledad, keymanager) config = Config() - leap_home = os.path.expanduser('~/.config/leap') + leap_home = os.path.join(get_path_prefix(), 'leap') config.leap_home = leap_home leap_session.config = config @@ -62,6 +62,20 @@ def start_pixelated_user_agent(userid, soledad, keymanager): return deferred +def get_smtp_config(provider): + config_path = os.path.join( + get_path_prefix(), 'leap', 'providers', provider, 'smtp-service.json') + json_config = json.loads(open(config_path).read()) + chosen_host = json_config['hosts'].keys()[0] + hostname = json_config['hosts'][chosen_host]['hostname'] + port = json_config['hosts'][chosen_host]['port'] + + config = Config() + config.host = hostname + config.port = port + return config + + class LeapSessionAdapter(object): def __init__(self, userid, soledad, keymanager): @@ -69,6 +83,7 @@ class LeapSessionAdapter(object): self.soledad = soledad + # FIXME this expects a keymanager-like instance self.nicknym = Config() self.nicknym.keymanager = keymanager @@ -77,22 +92,23 @@ class LeapSessionAdapter(object): self.user_auth = Config() self.user_auth.uuid = soledad.uuid - # XXX what is this?? path to smtp-service? - # self.config = provider.config - # self.provider = provider - self.fresh_account = False self.incoming_mail_fetcher = None self.account = IMAPAccount(userid, soledad, defer.Deferred()) username, provider = userid.split('@') - smtp_client_cert = os.path.expanduser( - '~/.config/leap/providers/{provider}/keys/' - 'client/smtp_{username}.pem'.format( - provider=provider, username=username)) - # TODO --- get from config - smtp_host = 'antelope.mail.bitmask.net' - smtp_port = 2013 + smtp_client_cert = os.path.join( + get_path_prefix(), + 'leap', 'providers', provider, 'keys', + 'client', + 'smtp_{username}.pem'.format( + username=username)) + + assert(os.path.isfile(smtp_client_cert)) + + smtp_config = get_smtp_config(provider) + smtp_host = smtp_config.host + smtp_port = smtp_config.port self.smtp_config = LeapSMTPConfig( userid, -- cgit v1.2.3 From ae5663769f2231f51b1cf2682bc6b6039dc0ab13 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Mar 2016 19:10:24 -0400 Subject: [feature] pass the path to pixelated_www package --- src/leap/bitmask/pix.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index 96ca4299..8242255c 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -19,6 +19,7 @@ Pixelated plugin integration. """ import json import os +import sys from twisted.internet import defer from twisted.python import log @@ -26,6 +27,8 @@ from twisted.python import log from leap.bitmask.util import get_path_prefix from leap.mail.imap.account import IMAPAccount +import pixelated_www + from pixelated.adapter.mailstore import LeapMailStore from pixelated.adapter.welcome_mail import add_welcome_mail from pixelated.application import SingleUserServicesFactory @@ -49,12 +52,19 @@ def start_pixelated_user_agent(userid, soledad, keymanager): services_factory = SingleUserServicesFactory( UserAgentMode(is_single_user=True)) - resource = RootResource(services_factory) + if getattr(sys, 'frozen', False): + # we are running in a |PyInstaller| bundle + static_folder = os.path.join(sys._MEIPASS, 'pixelated_www') + else: + static_folder = os.path.abspath(pixelated_www.__path__) + + resource = RootResource(services_factory, static_folder=static_folder) + + config.host = 'localhost' config.port = 9090 config.sslkey = None config.sslcert = None - config.host = 'localhost' deferred = _start_in_single_user_mode( leap_session, config, -- cgit v1.2.3 From 098b0974925c44dad1e98352670959cba66f9c0a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:27:13 -0400 Subject: [bug] fix case for constant --- src/leap/bitmask/config/leapsettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index a060c8a4..d7590c24 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -353,7 +353,7 @@ class LeapSettings(object): :type skip: bool """ leap_assert_type(skip, bool) - self._settings.setvalue(self.skipfirstrun_key, skip) + self._settings.setValue(self.SKIPFIRSTRUN_KEY, skip) def get_pixelmail_enabled(self): return to_bool(self._settings.value(self.PIXELMAIL_KEY, False)) -- cgit v1.2.3 From 541cf77d2c7bc9202474cce4c10b535a9c225ccd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:28:52 -0400 Subject: [bug] set standalone flag properly otherwise, the configs are saved to the user home config folder. --- src/leap/bitmask/services/__init__.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index 54426669..d86f8aa4 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -154,6 +154,9 @@ def download_service_config(provider_config, service_config, # Not modified service_path = ("leap", "providers", provider_config.get_domain(), service_json) + + service_config.__class__.standalone = flags.STANDALONE + if res.status_code == 304: logger.debug( "{0} definition has not been modified".format( -- cgit v1.2.3 From 62e9ae1bc0e28961e10b46646f6131152844d94c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 28 Mar 2016 12:29:57 -0400 Subject: [bug] instantiate soledadconfig if needed --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index a10a2731..21cdee31 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -331,6 +331,9 @@ class SoledadBootstrapper(AbstractBootstrapper): :returns: the server url :rtype: unicode """ + if not self._soledad_config: + self._soledad_config = SoledadConfig() + # TODO: Select server based on timezone (issue #3308) server_dict = self._soledad_config.get_hosts() -- cgit v1.2.3 From fe96e2eef29a538b856f0d8b4bbf3940e42364c1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 1 Apr 2016 12:55:00 -0400 Subject: [pkg] update to versioneer 0.16 --- src/leap/bitmask/_version.py | 100 +++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 38 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index 8c507c92..f032a17a 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -6,7 +6,9 @@ # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.15 (https://github.com/warner/python-versioneer) +# versioneer-0.16 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" import errno import os @@ -16,6 +18,7 @@ import sys def get_keywords(): + """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call @@ -27,10 +30,11 @@ def get_keywords(): class VersioneerConfig: - pass + """Container for Versioneer configuration parameters.""" def get_config(): + """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() @@ -44,7 +48,7 @@ def get_config(): class NotThisMethod(Exception): - pass + """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} @@ -52,7 +56,9 @@ HANDLERS = {} def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): + """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f @@ -61,6 +67,7 @@ def register_vcs_handler(vcs, method): # decorator def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: @@ -94,8 +101,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): def versions_from_parentdir(parentdir_prefix, root, verbose): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes + both the project name and a version string. + """ dirname = os.path.basename(root) if not dirname.startswith(parentdir_prefix): if verbose: @@ -109,6 +119,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from @@ -133,6 +144,7 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") refnames = keywords["refnames"].strip() @@ -178,11 +190,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. + """Get version from 'git describe' in the root of the source tree. + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) @@ -191,10 +204,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - # if there is a tag, this yields TAG-NUM-gHEX[-dirty] - # if there are no tags, this yields HEX[-dirty] (no NUM) + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long"], + "--always", "--long", + "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: @@ -259,19 +273,21 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): - # now build up version string, with post-release "local version - # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + """Build up version string, with post-release "local version identifier". - # exceptions: - # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -289,11 +305,11 @@ def render_pep440(pieces): def render_pep440_pre(pieces): - # TAG[.post.devDISTANCE] . No -dirty - - # exceptions: - # 1: no tags. 0.post.devDISTANCE + """TAG[.post.devDISTANCE] -- No -dirty. + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -305,14 +321,15 @@ def render_pep440_pre(pieces): def render_pep440_post(pieces): - # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that - # .dev0 sorts backwards (a dirty tree will appear "older" than the - # corresponding clean one), but you shouldn't be releasing software with - # -dirty anyways. + """TAG[.postDISTANCE[.dev0]+gHEX] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -331,11 +348,13 @@ def render_pep440_post(pieces): def render_pep440_old(pieces): - # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + """TAG[.postDISTANCE[.dev0]] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -351,12 +370,13 @@ def render_pep440_old(pieces): def render_git_describe(pieces): - # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty - # --always' + """TAG[-DISTANCE-gHEX][-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always'. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -370,12 +390,14 @@ def render_git_describe(pieces): def render_git_describe_long(pieces): - # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty - # --always -long'. The distance/hash is unconditional. + """TAG-DISTANCE-gHEX[-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) @@ -388,6 +410,7 @@ def render_git_describe_long(pieces): def render(pieces, style): + """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), @@ -417,6 +440,7 @@ def render(pieces, style): def get_versions(): + """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which -- cgit v1.2.3 From 0ce1c02dd59df3e4f23ba0b90e67644258412533 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 4 Apr 2016 21:55:06 -0400 Subject: [bug] fail gracefully if no pixelated modules present currently, we're distributing the wheels for the pixelated modules under downloads.leap.se. bootstrap script tried to download the pixelated modules, but it fails on python versions < 2.7.9, apparently. as a workaround, I make the import of the pixelated modules a non-fatal error by setting a flag, and doing the launching of the pix UA conditional on a successful import. - Related: #8009 --- src/leap/bitmask/backend/components.py | 14 ++++++++------ src/leap/bitmask/pix.py | 26 +++++++++++++++----------- 2 files changed, 23 insertions(+), 17 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 0f8864c3..0c2b3280 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -38,7 +38,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_LINUX -from leap.bitmask.pix import start_pixelated_user_agent +from leap.bitmask import pix from leap.bitmask.provider.pinned import PinnedProviders from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services import get_supported @@ -1088,13 +1088,15 @@ class Mail(object): return threads.deferToThread(self._stop_imap_service) def start_pixelated_service(self, full_user_id): - reactor.callFromThread( - start_pixelated_user_agent, - full_user_id, - self._soledad_proxy, - self._keymanager_proxy) + if pix.HAS_PIXELATED: + reactor.callFromThread( + pix.start_pixelated_user_agent, + full_user_id, + self._soledad_proxy, + self._keymanager_proxy) def stop_pixelated_service(self): + # TODO stop it, somehow pass diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index 8242255c..a05a1d9c 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -27,17 +27,21 @@ from twisted.python import log from leap.bitmask.util import get_path_prefix from leap.mail.imap.account import IMAPAccount -import pixelated_www - -from pixelated.adapter.mailstore import LeapMailStore -from pixelated.adapter.welcome_mail import add_welcome_mail -from pixelated.application import SingleUserServicesFactory -from pixelated.application import UserAgentMode -from pixelated.application import start_site -from pixelated.bitmask_libraries.smtp import LeapSMTPConfig -from pixelated.bitmask_libraries.session import SessionCache -from pixelated.config import services -from pixelated.resources.root_resource import RootResource +try: + import pixelated_www + + from pixelated.adapter.mailstore import LeapMailStore + from pixelated.adapter.welcome_mail import add_welcome_mail + from pixelated.application import SingleUserServicesFactory + from pixelated.application import UserAgentMode + from pixelated.application import start_site + from pixelated.bitmask_libraries.smtp import LeapSMTPConfig + from pixelated.bitmask_libraries.session import SessionCache + from pixelated.config import services + from pixelated.resources.root_resource import RootResource + HAS_PIXELATED = True +except ImportError: + HAS_PIXELATED = False def start_pixelated_user_agent(userid, soledad, keymanager): -- cgit v1.2.3 From bb0dd2d30c7408cf3a50bafd8085aa69ca02e4f2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 15:39:55 -0400 Subject: [pkg] add standalone flag if running inside a frozen bin --- src/leap/bitmask/app.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index dc1ee6df..0c4c32e2 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -163,6 +163,9 @@ def start_app(): } flags.STANDALONE = opts.standalone + if getattr(sys, 'frozen', False): + flags.STANDALONE = True + flags.OFFLINE = opts.offline flags.MAIL_LOGFILE = opts.mail_log_file flags.APP_VERSION_CHECK = opts.app_version_check -- cgit v1.2.3 From e420ba19f79cf80c462ebdcf694a7cec453c73b2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 16:10:42 -0400 Subject: [pkg] adjust for the new cwd() inside bundle --- src/leap/bitmask/platform_init/initializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 4e8bbdbb..f0ec2d53 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -482,7 +482,7 @@ def _linux_install_missing_scripts(badexec, notfound): """ success = False installer_path = os.path.abspath( - os.path.join(os.getcwd(), "apps", "eip", "files")) + os.path.join(os.getcwd(), "..", "apps", "eip", "files")) install_helper = "leap-install-helper.sh" install_helper_path = os.path.join(installer_path, install_helper) -- cgit v1.2.3 From b8acf1a9df87945012387dced43f8de5e13164a0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 6 Apr 2016 16:11:14 -0400 Subject: [style] autopep8 --- src/leap/bitmask/platform_init/initializers.py | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index f0ec2d53..193bd80a 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -440,29 +440,29 @@ def _get_missing_complain_dialog(stuff): class ComplainDialog(QtGui.QDialog): def __init__(self, parent=None): - super(ComplainDialog, self).__init__(parent) + super(ComplainDialog, self).__init__(parent) - label = QtGui.QLabel(msgstr.NO_HELPERS) - label.setAlignment(QtCore.Qt.AlignLeft) + label = QtGui.QLabel(msgstr.NO_HELPERS) + label.setAlignment(QtCore.Qt.AlignLeft) - label2 = QtGui.QLabel(msgstr.EXPLAIN) - label2.setAlignment(QtCore.Qt.AlignLeft) + label2 = QtGui.QLabel(msgstr.EXPLAIN) + label2.setAlignment(QtCore.Qt.AlignLeft) - textedit = QtGui.QTextEdit() - textedit.setText("\n".join(stuff)) + textedit = QtGui.QTextEdit() + textedit.setText("\n".join(stuff)) - ok = QtGui.QPushButton() - ok.setText(self.tr("Ok, thanks")) - self.ok = ok - self.ok.clicked.connect(self.close) + ok = QtGui.QPushButton() + ok.setText(self.tr("Ok, thanks")) + self.ok = ok + self.ok.clicked.connect(self.close) - mainLayout = QtGui.QGridLayout() - mainLayout.addWidget(label, 0, 0) - mainLayout.addWidget(label2, 1, 0) - mainLayout.addWidget(textedit, 2, 0) - mainLayout.addWidget(ok, 3, 0) + mainLayout = QtGui.QGridLayout() + mainLayout.addWidget(label, 0, 0) + mainLayout.addWidget(label2, 1, 0) + mainLayout.addWidget(textedit, 2, 0) + mainLayout.addWidget(ok, 3, 0) - self.setLayout(mainLayout) + self.setLayout(mainLayout) msg = ComplainDialog() msg.setWindowTitle(msg.tr("Missing Bitmask helper files")) -- cgit v1.2.3 From 42e4458f3f37d50725a37a7e22835ab677cc24aa Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 7 Apr 2016 11:09:05 -0400 Subject: [style] pep8 --- src/leap/bitmask/__init__.py | 3 ++- src/leap/bitmask/app.py | 20 +++++++++++--------- src/leap/bitmask/config/leapsettings.py | 1 - src/leap/bitmask/util/keyring_helpers.py | 5 +++-- src/leap/bitmask/util/pastebin.py | 4 ++-- 5 files changed, 18 insertions(+), 15 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index 6ab55e53..c25ae999 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -19,6 +19,8 @@ Init file for leap.bitmask Initializes version and app info. """ +from ._version import get_versions + # HACK: This is a hack so that py2app copies _scrypt.so to the right # place, it can't be technically imported, but that doesn't matter # because the import is never executed @@ -45,7 +47,6 @@ def _is_release_version(version_str): return patch.isdigit() -from ._version import get_versions __version__ = get_versions()['version'] __version_hash__ = get_versions()['full-revisionid'] IS_RELEASE_VERSION = _is_release_version(__version__) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 0c4c32e2..0ae60e48 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -46,11 +46,7 @@ import os import platform import sys -if platform.system() == "Darwin": - # We need to tune maximum number of files, due to zmq usage - # we hit the limit. - import resource - resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) +import psutil from leap.bitmask import __version__ as VERSION from leap.bitmask.backend.backend_proxy import BackendProxy @@ -70,7 +66,12 @@ from leap.mail import __version__ as MAIL_VERSION import codecs codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) -import psutil + +if platform.system() == "Darwin": + # We need to tune maximum number of files, due to zmq usage + # we hit the limit. + import resource + resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) def kill_the_children(): @@ -226,9 +227,10 @@ def start_app(): backend_pid = None if not backend_running: frontend_pid = os.getpid() - backend_process = multiprocessing.Process(target=run_backend, - name='Backend', - args=(opts.danger, flags_dict, frontend_pid)) + backend_process = multiprocessing.Process( + target=run_backend, + name='Backend', + args=(opts.danger, flags_dict, frontend_pid)) # we don't set the 'daemon mode' since we need to start child processes # in the backend # backend_process.daemon = True diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index d7590c24..01900484 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -361,4 +361,3 @@ class LeapSettings(object): def set_pixelmail_enabled(self, enabled): leap_assert_type(enabled, bool) self._settings.setvalue(self.PIXELMAIL_KEY, enabled) - diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py index d81f39b1..c5181348 100644 --- a/src/leap/bitmask/util/keyring_helpers.py +++ b/src/leap/bitmask/util/keyring_helpers.py @@ -24,8 +24,9 @@ try: EncryptedKeyring, PlaintextKeyring ] - canuse = lambda kr: (kr is not None - and kr.__class__ not in OBSOLETE_KEYRINGS) + canuse = lambda kr: ( + kr is not None and + kr.__class__ not in OBSOLETE_KEYRINGS) except Exception: # Problems when importing keyring! It might be a problem binding to the diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py index 6d50ac9e..b476ad88 100644 --- a/src/leap/bitmask/util/pastebin.py +++ b/src/leap/bitmask/util/pastebin.py @@ -25,13 +25,13 @@ ############################################################################# +import urllib + __ALL__ = ['delete_paste', 'user_details', 'trending', 'pastes_by_user', 'generate_user_key', 'paste', 'Pastebin', 'PastebinError', 'PostLimitError'] -import urllib - class PastebinError(RuntimeError): """Pastebin API error. -- cgit v1.2.3 From 3340e1a898bacdd05fb6e6bf8d37596f7aff81a2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 7 Apr 2016 09:56:36 -0400 Subject: [feat] use same token for imap/smtp authentication This greatly simplifies the handling of the password in the thunderbird extension. - Related: #6041 --- src/leap/bitmask/backend/components.py | 7 ++----- src/leap/bitmask/gui/mainwindow.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 13 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 0c2b3280..f9ad1480 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -806,11 +806,8 @@ class Soledad(object): (service, token)) sol = self._soledad_bootstrapper.soledad - d = sol.get_or_create_service_token('imap') - d.addCallback(register_service_token, 'imap') - d.addCallback( - lambda _: sol.get_or_create_service_token('smtp')) - d.addCallback(register_service_token, 'smtp') + d = sol.get_or_create_service_token('mail_auth') + d.addCallback(register_service_token, 'mail_auth') d.addCallback(lambda _: result) return d diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 839aae87..ace3f863 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1089,12 +1089,11 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # FIXME on i3, this doens't allow to mouse-select. # Switch to a dialog in which we can set the QLabel - imap_token = (self._service_tokens.get('imap', None) or - "??? (log in to unlock)") - smtp_token = (self._service_tokens.get('smtp', None) or - "??? (log in to unlock)") - imap_password = self.tr("IMAP Password:") + " %s" % (imap_token,) - smtp_password = self.tr("SMTP Password:") + " %s" % (smtp_token,) + mail_auth_token = ( + self._service_tokens.get('mail_auth', None) or + "??? (log in to unlock)") + mail_password = self.tr("IMAP/SMTP Password:") + " %s" % ( + mail_auth_token,) msg = help_url + self.tr( "

    {0}

    " @@ -1105,10 +1104,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): "
  •  {4}
  • " "
  •  {5}
  • " "
  •  {6}
  • " - "
  •  {7}
  • " "

    ").format(email_quick_reference, thunderbird_text, manual_text, manual_imap, manual_smtp, - manual_username, imap_password, smtp_password) + manual_username, mail_password) QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) def _needs_update(self): -- cgit v1.2.3 From 939fefc012213d3aa433caec47e2e0b19d64901e Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 30 Mar 2016 13:51:01 +0200 Subject: [feat] Write service tokens to a file The thunderbird plugin will read the tokens from there. - Related: #6041 --- src/leap/bitmask/backend/components.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index f9ad1480..bc34c84c 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -20,8 +20,11 @@ Backend components # TODO [ ] Get rid of all this deferToThread mess, or at least contain # all of it into its own threadpool. +import json import os +import shutil import socket +import tempfile import time from functools import partial @@ -789,6 +792,8 @@ class Soledad(object): download_if_needed=True) self._soledad_defer.addCallback(self._set_proxies_cb) self._soledad_defer.addCallback(self._set_service_tokens_cb) + self._soledad_defer.addCallback(self._write_tokens_file, + username, domain) else: if self._signaler is not None: self._signaler.signal(self._signaler.soledad_bootstrap_failed) @@ -811,6 +816,23 @@ class Soledad(object): d.addCallback(lambda _: result) return d + def _write_tokens_file(self, result, username, domain): + tokens_folder = os.path.join(tempfile.gettempdir(), "bitmask_tokens") + if os.path.exists(tokens_folder): + try: + shutil.rmtree(tokens_folder) + except OSError as e: + logger.error("Can't remove tokens folder %s: %s" + % (tokens_folder, e)) + return + os.mkdir(tokens_folder, 0700) + + tokens_path = os.path.join(tokens_folder, + "%s@%s.json" % (username, domain)) + with open(tokens_path, 'w') as ftokens: + json.dump(self._service_tokens, ftokens) + return result + def _set_proxies_cb(self, _): """ Update the soledad and keymanager proxies to reference the ones created -- cgit v1.2.3 From 130f1a8753bfe63f5575fc2011a15af9a0752170 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 7 Apr 2016 12:18:24 -0400 Subject: [bug] allow resizing browser window --- src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/gui/qt_browser.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index ace3f863..168de8ed 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -579,6 +579,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): def _show_pixelated_browser(self): win = PixelatedWindow(self) win.show() + win.load_app() def _update_eip_enabled_status(self, account=None, services=None): """ diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index e480b363..b75bfb64 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -26,8 +26,14 @@ PIXELATED_URI = 'http://localhost:9090' class PixelatedWindow(QtGui.QDialog): def __init__(self, parent): - QtGui.QDialog.__init__(self, parent) - self.web = QtWebKit.QWebView(self) - self.web.load(QtCore.QUrl(PIXELATED_URI)) + super(PixelatedWindow, self).__init__(parent) + self.view = QtWebKit.QWebView(self) + + layout = QtGui.QGridLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.view) + self.setLayout(layout) self.setWindowTitle('Bitmask/Pixelated WebMail') - self.web.show() + + def load_app(self): + self.view.load(QtCore.QUrl(PIXELATED_URI)) -- cgit v1.2.3 From fcb9513a8df9b2d690607106d3397b7c785f3b33 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 8 Apr 2016 01:08:53 -0400 Subject: [bug] fix pixelated-www path --- src/leap/bitmask/pix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index a05a1d9c..aa18c589 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -61,7 +61,7 @@ def start_pixelated_user_agent(userid, soledad, keymanager): # we are running in a |PyInstaller| bundle static_folder = os.path.join(sys._MEIPASS, 'pixelated_www') else: - static_folder = os.path.abspath(pixelated_www.__path__) + static_folder = os.path.abspath(pixelated_www.__path__[0]) resource = RootResource(services_factory, static_folder=static_folder) -- cgit v1.2.3 From 977f594510b70c7f0d1c99d1d77c188ff6ba8e37 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 11 Apr 2016 17:47:50 -0400 Subject: [bug] workaround for qt gui corruption in wily - Resolves: #8028 --- src/leap/bitmask/app.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 0ae60e48..2c41068c 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -74,6 +74,11 @@ if platform.system() == "Darwin": resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) +def qt_hack_ubuntu(): + """Export an env var to avoid gui corruption, see #8028""" + os.environ['QT_GRAPHICSSYSTEM'] = 'native' + + def kill_the_children(): """ Make sure no lingering subprocesses are left in case of a bad termination. @@ -151,6 +156,8 @@ def start_app(): """ Starts the main event loop and launches the main window. """ + qt_hack_ubuntu() + # Ignore the signals since we handle them in the subprocesses # signal.signal(signal.SIGINT, signal.SIG_IGN) -- cgit v1.2.3 From 42bb82b06ea313877023da9f689782b228fac345 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 13 Apr 2016 11:03:20 -0400 Subject: [feat] Adapt to latest pixelated-ua In the latest snapshots, pixelated has migrated to use Account instead of IMAPAccount. Also, created an adaptor for Nicknym that allows to be initialized with just the userid and the keymanager instance. This is ugly and has to go when we converge on a single codebase (bitmask.core, hopefully). --- src/leap/bitmask/pix.py | 56 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 12 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index aa18c589..8d7d0811 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -25,11 +25,10 @@ from twisted.internet import defer from twisted.python import log from leap.bitmask.util import get_path_prefix -from leap.mail.imap.account import IMAPAccount +from leap.mail.mail import Account +from leap.keymanager import openpgp, KeyNotFound try: - import pixelated_www - from pixelated.adapter.mailstore import LeapMailStore from pixelated.adapter.welcome_mail import add_welcome_mail from pixelated.application import SingleUserServicesFactory @@ -39,6 +38,7 @@ try: from pixelated.bitmask_libraries.session import SessionCache from pixelated.config import services from pixelated.resources.root_resource import RootResource + import pixelated_www HAS_PIXELATED = True except ImportError: HAS_PIXELATED = False @@ -70,10 +70,11 @@ def start_pixelated_user_agent(userid, soledad, keymanager): config.sslkey = None config.sslcert = None - deferred = _start_in_single_user_mode( - leap_session, config, - resource, services_factory) - return deferred + d = leap_session.account.callWhenReady( + lambda _: _start_in_single_user_mode( + leap_session, config, + resource, services_factory)) + return d def get_smtp_config(provider): @@ -90,6 +91,39 @@ def get_smtp_config(provider): return config +class NickNym(object): + + def __init__(self, keymanager, userid): + self._email = userid + self.keymanager = keymanager + + @defer.inlineCallbacks + def generate_openpgp_key(self): + key_present = yield self._key_exists(self._email) + if not key_present: + yield self._gen_key() + yield self._send_key_to_leap() + + @defer.inlineCallbacks + def _key_exists(self, email): + try: + yield self.fetch_key(email, private=True, fetch_remote=False) + defer.returnValue(True) + except KeyNotFound: + defer.returnValue(False) + + def fetch_key(self, email, private=False, fetch_remote=True): + return self.keymanager.get_key( + email, openpgp.OpenPGPKey, + private=private, fetch_remote=fetch_remote) + + def _gen_key(self): + return self.keymanager.gen_key(openpgp.OpenPGPKey) + + def _send_key_to_leap(self): + return self.keymanager.send_key(openpgp.OpenPGPKey) + + class LeapSessionAdapter(object): def __init__(self, userid, soledad, keymanager): @@ -97,10 +131,8 @@ class LeapSessionAdapter(object): self.soledad = soledad - # FIXME this expects a keymanager-like instance - self.nicknym = Config() - self.nicknym.keymanager = keymanager - + # XXX this needs to be converged with our public apis. + self.nicknym = NickNym(keymanager, userid) self.mail_store = LeapMailStore(soledad) self.user_auth = Config() @@ -108,7 +140,7 @@ class LeapSessionAdapter(object): self.fresh_account = False self.incoming_mail_fetcher = None - self.account = IMAPAccount(userid, soledad, defer.Deferred()) + self.account = Account(soledad) username, provider = userid.split('@') smtp_client_cert = os.path.join( -- cgit v1.2.3 From 6e2e5e08dca279b6ea4f7835415a3900c28dfd84 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 14 Apr 2016 18:01:28 -0400 Subject: [bug] another workaround for the transparent window in wily The previous "fix" attempt with QT_GRAPHICSSYSTEM=native apparently wasn't fixing the issue consistently. This extra env var works 100% of the times by my tests though. - Resolves: #8028 --- src/leap/bitmask/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 2c41068c..9412ccd7 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -75,8 +75,9 @@ if platform.system() == "Darwin": def qt_hack_ubuntu(): - """Export an env var to avoid gui corruption, see #8028""" + """Export two env vars to avoid gui corruption, see #8028""" os.environ['QT_GRAPHICSSYSTEM'] = 'native' + os.environ['LIBOVERLAY_SCROLLBAR'] = '0' def kill_the_children(): -- cgit v1.2.3 From 8361cfdf5f6f56da8e1f7297092d1458a72f9444 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 14 Apr 2016 12:39:26 -0700 Subject: [bug] eip_can_start should return false if provider does not support eip otherwise, it tries to open eip config. - Resolves: #7538 - Releases: 0.9.2 --- src/leap/bitmask/backend/components.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index bc34c84c..03a92c88 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -44,7 +44,7 @@ from leap.bitmask.platform_init import IS_LINUX from leap.bitmask import pix from leap.bitmask.provider.pinned import PinnedProviders from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services import get_supported +from leap.bitmask.services import get_supported, EIP_SERVICE from leap.bitmask.services.eip import eipconfig from leap.bitmask.services.eip import get_openvpn_management from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper @@ -651,8 +651,10 @@ class EIP(object): logger.error("No polkit agent running.") return False - eip_config = eipconfig.EIPConfig() provider_config = ProviderConfig.get_provider_config(domain) + if EIP_SERVICE not in provider_config.get_services(): + return False + eip_config = eipconfig.EIPConfig() api_version = provider_config.get_api_version() eip_config.set_api_version(api_version) -- cgit v1.2.3 From 92f4b40ab48ec537aade244af3e3e4f2c17b1475 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 15 Apr 2016 16:10:00 -0400 Subject: [refactor] adapt to the new Account signature --- src/leap/bitmask/pix.py | 2 +- src/leap/bitmask/services/mail/imap.py | 2 +- src/leap/bitmask/services/mail/plumber.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/pix.py b/src/leap/bitmask/pix.py index 8d7d0811..b510106d 100644 --- a/src/leap/bitmask/pix.py +++ b/src/leap/bitmask/pix.py @@ -140,7 +140,7 @@ class LeapSessionAdapter(object): self.fresh_account = False self.incoming_mail_fetcher = None - self.account = Account(soledad) + self.account = Account(soledad, userid) username, provider = userid.split('@') smtp_client_cert = os.path.join( diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 54935f8c..2f000b2a 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -91,7 +91,7 @@ def start_incoming_mail_service(keymanager, soledad, userid): check_period=get_mail_check_period()) return incoming_mail - acc = Account(soledad) + acc = Account(soledad, userid) d = acc.callWhenReady(lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) d.addErrback(log.err) diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py index 43203f0c..cd1f06bb 100644 --- a/src/leap/bitmask/services/mail/plumber.py +++ b/src/leap/bitmask/services/mail/plumber.py @@ -60,6 +60,7 @@ def initialize_soledad(uuid, email, passwd, cert_file = "" class Mock(object): + def __init__(self, return_value=None): self._return = return_value @@ -140,7 +141,7 @@ class MBOXPlumber(object): self.sol = initialize_soledad( self.uuid, self.userid, self.passwd, secrets, localdb, "/tmp", "/tmp") - self.acct = IMAPAccount(self.userid, self.sol) + self.acct = IMAPAccount(self.sol, self.userid) return True # -- cgit v1.2.3 From 748dea614050dca9d1baf8e6b36900109066b2bb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 11 Sep 2015 07:44:14 -0700 Subject: [pkg] packaging hacks + update osx bundling notes --- src/leap/bitmask/_components.py | 2 +- src/leap/bitmask/_version.py | 1 - src/leap/bitmask/platform_init/initializers.py | 3 +++ 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/_components.py b/src/leap/bitmask/_components.py index 9be0e6bc..9d6f3f59 100644 --- a/src/leap/bitmask/_components.py +++ b/src/leap/bitmask/_components.py @@ -2,5 +2,5 @@ Enabled Modules in Bitmask. Change these values for builds of the client with only one module enabled. """ -HAS_EIP = True +HAS_EIP = False HAS_MAIL = True diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index f032a17a..d64032a7 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -1,4 +1,3 @@ - # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 193bd80a..e8d48e4a 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -368,6 +368,9 @@ def DarwinInitializer(): Raise a dialog in case that the osx tuntap driver has not been found in the registry, asking the user for permission to install the driver """ + logger.debug("Skipping darwin initialization, only-mail build") + return True + # XXX split this function into several TUNTAP_NOTFOUND_MSG = NOTFOUND_MSG % ( -- cgit v1.2.3 From e9e9abc4ec26be29b3a6b09e6a0b67786269183b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 29 Jan 2016 13:18:36 -0800 Subject: [feature] privileged bitmask helper This is still quite untested, and a bit hacky, but the main idea behind let us have a daemonized bitmask helper, that should be installed by the Bitmask installer. Its responsibilities are to launch the vpn process as a privileged user, and start/stop the firewall. --- src/leap/bitmask/_components.py | 2 +- src/leap/bitmask/backend/components.py | 2 +- src/leap/bitmask/services/eip/darwinvpnlauncher.py | 63 ++++--- src/leap/bitmask/services/eip/vpnlauncher.py | 8 +- src/leap/bitmask/services/eip/vpnprocess.py | 188 +++++++++++++++------ 5 files changed, 188 insertions(+), 75 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/_components.py b/src/leap/bitmask/_components.py index 9d6f3f59..9be0e6bc 100644 --- a/src/leap/bitmask/_components.py +++ b/src/leap/bitmask/_components.py @@ -2,5 +2,5 @@ Enabled Modules in Bitmask. Change these values for builds of the client with only one module enabled. """ -HAS_EIP = False +HAS_EIP = True HAS_MAIL = True diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 03a92c88..acb562c7 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -647,7 +647,7 @@ class EIP(object): :param domain: the domain for the provider to check :type domain: str """ - if not LinuxPolicyChecker.is_up(): + if IS_LINUX and not LinuxPolicyChecker.is_up(): logger.error("No polkit agent running.") return False diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index 17fc11c2..42d9576b 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -20,6 +20,7 @@ Darwin VPN launcher implementation. import commands import getpass import os +import socket import sys from leap.bitmask.logs.utils import get_logger @@ -34,17 +35,47 @@ class EIPNoTunKextLoaded(VPNLauncherException): pass +class DarwinHelperCommand(object): + + SOCKET_ADDR = '/tmp/bitmask-helper.socket' + + def __init__(self): + pass + + def _connect(self): + self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + self._sock.connect(self.SOCKET_ADDR) + except socket.error, msg: + raise RuntimeError(msg) + + def send(self, cmd, args=''): + # TODO check cmd is in allowed list + self._connect() + sock = self._sock + data = "" + + command = cmd + ' ' + args + '/CMD' + + try: + sock.sendall(command) + while '\n' not in data: + data += sock.recv(32) + finally: + sock.close() + + return data + + class DarwinVPNLauncher(VPNLauncher): """ VPN launcher for the Darwin Platform """ - COCOASUDO = "cocoasudo" - # XXX need the good old magic translate for these strings - # (look for magic in 0.2.0 release) - SUDO_MSG = ("Bitmask needs administrative privileges to run " - "Encrypted Internet.") - INSTALL_MSG = ("\"Bitmask needs administrative privileges to install " - "missing scripts and fix permissions.\"") + UP_SCRIPT = None + DOWN_SCRIPT = None + + # TODO -- move this to bitmask-helper + # Hardcode the installation path for OSX for security, openvpn is # run as root @@ -56,14 +87,9 @@ class DarwinVPNLauncher(VPNLauncher): INSTALL_PATH_ESCAPED,) OPENVPN_BIN_PATH = "%s/Contents/Resources/%s" % (INSTALL_PATH, OPENVPN_BIN) - - UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,) - DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,) - OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,) - - UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN) OTHER_FILES = [] + # TODO deprecate ------------------------------------------------ @classmethod def cmd_for_missing_scripts(kls, frompath): """ @@ -87,7 +113,7 @@ class DarwinVPNLauncher(VPNLauncher): :returns: True if kext is loaded, False otherwise. :rtype: bool """ - return bool(commands.getoutput('kextstat | grep "leap.tun"')) + return bool(commands.getoutput('kextstat | grep "foo.tun"')) @classmethod def _get_icon_path(kls): @@ -101,6 +127,8 @@ class DarwinVPNLauncher(VPNLauncher): return os.path.join(resources_path, "bitmask.tiff") + + # TODO deprecate --------------------------------------------------------- @classmethod def get_cocoasudo_ovpn_cmd(kls): """ @@ -120,6 +148,7 @@ class DarwinVPNLauncher(VPNLauncher): return kls.COCOASUDO, args + # TODO deprecate --------------------------------------------------------- @classmethod def get_cocoasudo_installmissing_cmd(kls): """ @@ -171,12 +200,6 @@ class DarwinVPNLauncher(VPNLauncher): # we use `super` in order to send the class to use command = super(DarwinVPNLauncher, kls).get_vpn_command( eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) - - cocoa, cargs = kls.get_cocoasudo_ovpn_cmd() - cargs.extend(command) - command = cargs - command.insert(0, cocoa) - command.extend(['--setenv', "LEAPUSER", getpass.getuser()]) return command diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index c48f857c..16dfd9cf 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -29,7 +29,7 @@ from leap.bitmask.config import flags from leap.bitmask.logs.utils import get_logger from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.platform_init import IS_LINUX +from leap.bitmask.platform_init import IS_LINUX, IS_MAC from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector from leap.bitmask.util import force_eval from leap.common.check import leap_assert, leap_assert_type @@ -286,8 +286,8 @@ class VPNLauncher(object): :rtype: list """ # FIXME - # XXX remove method when we ditch UPDOWN in osx and win too - if IS_LINUX: + # XXX remove method when we ditch UPDOWN in win too + if IS_LINUX or IS_MAC: return [] else: leap_assert(kls.UPDOWN_FILES is not None, @@ -308,7 +308,7 @@ class VPNLauncher(object): """ leap_assert(kls.OTHER_FILES is not None, "Need to define OTHER_FILES for this particular " - "auncher before calling this method") + "launcher before calling this method") other = force_eval(kls.OTHER_FILES) file_exist = partial(_has_other_files, warn=False) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 586b50f5..6d18a599 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -23,6 +23,7 @@ import shutil import socket import subprocess import sys +import time from itertools import chain, repeat @@ -41,6 +42,7 @@ from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.logs.utils import get_logger from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip import linuxvpnlauncher +from leap.bitmask.services.eip import darwinvpnlauncher from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.bitmask.services.eip.udstelnet import UDSTelnet from leap.bitmask.util import first, force_eval @@ -145,7 +147,7 @@ class VPN(object): demand. """ TERMINATE_MAXTRIES = 10 - TERMINATE_WAIT = 1 # secs + TERMINATE_WAIT = 2 # secs OPENVPN_VERB = "openvpn_verb" @@ -173,7 +175,7 @@ class VPN(object): :param kwargs: kwargs to be passed to the VPNProcess :type kwargs: dict """ - logger.debug('VPN: start') + logger.debug('VPN: start ---------------------------------------------------') self._user_stopped = False self._stop_pollers() kwargs['openvpn_verb'] = self._openvpn_verb @@ -181,22 +183,6 @@ class VPN(object): restart = kwargs.pop('restart', False) - # start the main vpn subprocess - vpnproc = VPNProcess(*args, **kwargs) - - if vpnproc.get_openvpn_process(): - logger.info("Another vpn process is running. Will try to stop it.") - vpnproc.stop_if_already_running() - - # we try to bring the firewall up - if IS_LINUX: - gateways = vpnproc.getGateways() - firewall_up = self._launch_firewall(gateways, - restart=restart) - if not restart and not firewall_up: - logger.error("Could not bring firewall up, " - "aborting openvpn launch.") - return # FIXME it would be good to document where the # errors here are catched, since we currently handle them @@ -211,18 +197,56 @@ class VPN(object): # the ping-pong to the frontend, and without adding any logical checks # in the frontend. We should just communicate UI changes to frontend, # and abstract us away from anything else. - try: + + # TODO factor this out to the platform-launchers + + if IS_LINUX: + # start the main vpn subprocess + vpnproc = VPNProcess(*args, **kwargs) cmd = vpnproc.getCommand() - except Exception as e: - logger.error("Error while getting vpn command... {0!r}".format(e)) - raise + + if vpnproc.get_openvpn_process(): + logger.info("Another vpn process is running. Will try to stop it.") + vpnproc.stop_if_already_running() + + # we try to bring the firewall up + gateways = vpnproc.getGateways() + firewall_up = self._launch_firewall_linux( + gateways, restart=restart) + if not restart and not firewall_up: + logger.error("Could not bring firewall up, " + "aborting openvpn launch.") + return + + if IS_MAC: + # start the main vpn subprocess + vpnproc = VPNCanary(*args, **kwargs) + + # we try to bring the firewall up + gateways = vpnproc.getGateways() + firewall_up = self._launch_firewall_osx( + gateways, restart=restart) + if not restart and not firewall_up: + logger.error("Could not bring firewall up, " + "aborting openvpn launch.") + return + + helper = darwinvpnlauncher.DarwinHelperCommand() + cmd = vpnproc.getVPNCommand() + result = helper.send('openvpn_start %s' % ' '.join(cmd)) + + # TODO Windows version -- should be similar to osx. env = os.environ for key, val in vpnproc.vpn_env.items(): env[key] = val - reactor.spawnProcess(vpnproc, cmd[0], cmd, env) + cmd = vpnproc.getCommand() + running_proc = reactor.spawnProcess(vpnproc, cmd[0], cmd, env) + vpnproc.pid = running_proc.pid self._vpnproc = vpnproc + + # add pollers for status and state # this could be extended to a collection of @@ -233,9 +257,9 @@ class VPN(object): self._pollers.extend(poll_list) self._start_pollers() - def _launch_firewall(self, gateways, restart=False): + def _launch_firewall_linux(self, gateways, restart=False): """ - Launch the firewall using the privileged wrapper. + Launch the firewall using the privileged wrapper (linux). :param gateways: :type gateways: list @@ -254,40 +278,63 @@ class VPN(object): exitCode = subprocess.call(cmd + gateways) return True if exitCode is 0 else False + def _launch_firewall_osx(self, gateways, restart=False): + cmd = 'firewall_start %s' % ' '.join(gateways) + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) + return True + + # TODO -- write LINUX/OSX VERSION too ------------------------------------ def is_fw_down(self): """ Return whether the firewall is down or not. :rtype: bool """ - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) - fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT) - fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 - return fw_is_down() + if IS_LINUX: + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT) + fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 + return fw_is_down() + + if IS_MAC: + cmd = 'firewall_isup' + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) + return True + def tear_down_firewall(self): """ Tear the firewall down using the privileged wrapper. """ if IS_MAC: - # We don't support Mac so far + cmd = 'firewall_stop' + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) return True - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) - exitCode = subprocess.call(["pkexec", - BM_ROOT, "firewall", "stop"]) - return True if exitCode is 0 else False + + if IS_LINUX: + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + exitCode = subprocess.call(["pkexec", + BM_ROOT, "firewall", "stop"]) + return True if exitCode is 0 else False def bitmask_root_vpn_down(self): """ Bring openvpn down using the privileged wrapper. """ if IS_MAC: - # We don't support Mac so far + cmd = 'openvpn_stop' + helper = darwinvpnlauncher.DarwinHelperCommand() + result = helper.send(cmd) return True - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) - exitCode = subprocess.call(["pkexec", - BM_ROOT, "openvpn", "stop"]) - return True if exitCode is 0 else False + + if IS_LINUX: + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + exitCode = subprocess.call(["pkexec", + BM_ROOT, "openvpn", "stop"]) + return True if exitCode is 0 else False def _kill_if_left_alive(self, tries=0): """ @@ -297,18 +344,18 @@ class VPN(object): :param tries: counter of tries, used in recursion :type tries: int """ + # we try to tear the firewall down + if (IS_LINUX or IS_MAC) and self._user_stopped: + logger.debug('trying to bring firewall down...') + firewall_down = self.tear_down_firewall() + if firewall_down: + logger.debug("Firewall down") + else: + logger.warning("Could not tear firewall down") + while tries < self.TERMINATE_MAXTRIES: if self._vpnproc.transport.pid is None: logger.debug("Process has been happily terminated.") - - # we try to tear the firewall down - if IS_LINUX and self._user_stopped: - firewall_down = self.tear_down_firewall() - if firewall_down: - logger.debug("Firewall down") - else: - logger.warning("Could not tear firewall down") - return else: logger.debug("Process did not die, waiting...") @@ -813,6 +860,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): programmatically. """ + pid = None + def __init__(self, eipconfig, providerconfig, socket_host, socket_port, signaler, openvpn_verb): """ @@ -861,7 +910,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): self._vpn_observer = VPNObserver(signaler) self.is_restart = False - # processProtocol methods + # ProcessProtocol methods def connectionMade(self): """ @@ -893,8 +942,11 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa """ exit_code = reason.value.exitCode + if isinstance(exit_code, int): logger.debug("processExited, status %d" % (exit_code,)) + else: + exit_code = 0 self._signaler.signal( self._signaler.eip_process_finished, exit_code) self._alive = False @@ -976,3 +1028,41 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): self.transport.signalProcess('KILL') except internet_error.ProcessExitedAlready: logger.debug('Process Exited Already') + + +class VPNCanary(VPNProcess): + + """ + This is a Canary Process that does not run openvpn itself, but it's + notified by the privileged process when the process dies. + + This is an ugly workaround to allow the qt signals and the processprotocol + to live happily together until we refactor EIP out of the qt model + completely. + """ + + def connectionMade(self): + VPNProcess.connectionMade(self) + reactor.callLater(2, self.registerPID) + + def registerPID(self): + helper = darwinvpnlauncher.DarwinHelperCommand() + cmd = 'openvpn_set_watcher %s' % self.pid + result = helper.send(cmd) + + def killProcess(self): + helper = darwinvpnlauncher.DarwinHelperCommand() + cmd = 'openvpn_force_stop' + result = helper.send(cmd) + + def getVPNCommand(self): + return VPNProcess.getCommand(self) + + def getCommand(self): + canary = '''import sys, signal, time +def receive_signal(signum, stack): + sys.exit() +signal.signal(signal.SIGTERM, receive_signal) +while True: + time.sleep(60)''' + return ['python', '-c', '%s' % canary] -- cgit v1.2.3 From 9affaaacb18598fc98be669ef1c086b0afe4ad91 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 11 Feb 2016 13:34:46 -0800 Subject: [refactor] cleanup helper usage to adapt to new one also cleanups build process --- src/leap/bitmask/services/eip/darwinvpnlauncher.py | 4 +--- src/leap/bitmask/services/eip/vpnprocess.py | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index 42d9576b..f1d17698 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -54,7 +54,7 @@ class DarwinHelperCommand(object): self._connect() sock = self._sock data = "" - + command = cmd + ' ' + args + '/CMD' try: @@ -76,7 +76,6 @@ class DarwinVPNLauncher(VPNLauncher): # TODO -- move this to bitmask-helper - # Hardcode the installation path for OSX for security, openvpn is # run as root INSTALL_PATH = "/Applications/Bitmask.app/" @@ -127,7 +126,6 @@ class DarwinVPNLauncher(VPNLauncher): return os.path.join(resources_path, "bitmask.tiff") - # TODO deprecate --------------------------------------------------------- @classmethod def get_cocoasudo_ovpn_cmd(kls): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 6d18a599..de8d92f3 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -175,7 +175,8 @@ class VPN(object): :param kwargs: kwargs to be passed to the VPNProcess :type kwargs: dict """ - logger.debug('VPN: start ---------------------------------------------------') + logger.debug( + 'VPN: start ---------------------------------------------------') self._user_stopped = False self._stop_pollers() kwargs['openvpn_verb'] = self._openvpn_verb @@ -183,7 +184,6 @@ class VPN(object): restart = kwargs.pop('restart', False) - # FIXME it would be good to document where the # errors here are catched, since we currently handle them # at the frontend layer. This *should* move to be handled entirely @@ -206,7 +206,8 @@ class VPN(object): cmd = vpnproc.getCommand() if vpnproc.get_openvpn_process(): - logger.info("Another vpn process is running. Will try to stop it.") + logger.info( + "Another vpn process is running. Will try to stop it.") vpnproc.stop_if_already_running() # we try to bring the firewall up @@ -245,8 +246,6 @@ class VPN(object): running_proc = reactor.spawnProcess(vpnproc, cmd[0], cmd, env) vpnproc.pid = running_proc.pid self._vpnproc = vpnproc - - # add pollers for status and state # this could be extended to a collection of @@ -292,7 +291,8 @@ class VPN(object): :rtype: bool """ if IS_LINUX: - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + BM_ROOT = force_eval( + linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT) fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 return fw_is_down() @@ -303,7 +303,6 @@ class VPN(object): result = helper.send(cmd) return True - def tear_down_firewall(self): """ Tear the firewall down using the privileged wrapper. @@ -315,7 +314,8 @@ class VPN(object): return True if IS_LINUX: - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + BM_ROOT = force_eval( + linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) exitCode = subprocess.call(["pkexec", BM_ROOT, "firewall", "stop"]) return True if exitCode is 0 else False @@ -331,7 +331,8 @@ class VPN(object): return True if IS_LINUX: - BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) + BM_ROOT = force_eval( + linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) exitCode = subprocess.call(["pkexec", BM_ROOT, "openvpn", "stop"]) return True if exitCode is 0 else False -- cgit v1.2.3 From d01772d0a83535bf45fa43786213a9a0fcb232bc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 11 Feb 2016 13:34:46 -0800 Subject: [refactor] cleanup helper usage to adapt to new one also cleanups build process --- src/leap/bitmask/services/eip/darwinvpnlauncher.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index f1d17698..94161192 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -112,7 +112,10 @@ class DarwinVPNLauncher(VPNLauncher): :returns: True if kext is loaded, False otherwise. :rtype: bool """ - return bool(commands.getoutput('kextstat | grep "foo.tun"')) + loaded = bool(commands.getoutput('kextstat | grep "net.sf.tuntaposx.tun"')) + if not loaded: + logger.error("tuntaposx extension not loaded!") + return loaded @classmethod def _get_icon_path(kls): -- cgit v1.2.3 From b91263cba4078a7c4d19de0c31060cb7564ae410 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 17 Apr 2016 08:52:26 -0700 Subject: [bug] enable first page in wizard - Resolves: #8041 - Releases: 0.9.2 --- src/leap/bitmask/gui/mainwindow.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 168de8ed..20909038 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -448,6 +448,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # Refer to http://www.themacaque.com/?p=1067 for funny details. self._wizard.show() if IS_MAC: + # XXX hack. For some reason, there's a signal that doesn't arrive + # on time, so that the next button is disabled. See #8041 + self._wizard.page(self._wizard.INTRO_PAGE).set_completed() self._wizard.raise_() self._settings.set_skip_first_run(True) -- cgit v1.2.3 From c36e86275fe1c9ca4d6932c5103f91f5c2473b2e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 17 Apr 2016 12:50:59 -0700 Subject: [bug] avoid osx hanging --- src/leap/bitmask/app.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 9412ccd7..31dba157 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -46,7 +46,15 @@ import os import platform import sys -import psutil + +if platform.system() == "Darwin": + # XXX please ignore pep8 complains, this needs to be executed + # early. + # We need to tune maximum number of files, due to zmq usage + # we hit the limit. + import resource + resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) + from leap.bitmask import __version__ as VERSION from leap.bitmask.backend.backend_proxy import BackendProxy @@ -66,13 +74,7 @@ from leap.mail import __version__ as MAIL_VERSION import codecs codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) - -if platform.system() == "Darwin": - # We need to tune maximum number of files, due to zmq usage - # we hit the limit. - import resource - resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) - +import psutil def qt_hack_ubuntu(): """Export two env vars to avoid gui corruption, see #8028""" @@ -172,8 +174,11 @@ def start_app(): } flags.STANDALONE = opts.standalone - if getattr(sys, 'frozen', False): - flags.STANDALONE = True + + if platform.system() != 'Darwin': + # XXX this hangs the OSX bundles. + if getattr(sys, 'frozen', False): + flags.STANDALONE = True flags.OFFLINE = opts.offline flags.MAIL_LOGFILE = opts.mail_log_file -- cgit v1.2.3 From 9c3a713ff33c87af03b31ec9ad018a6ca3dfbb97 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 18 Apr 2016 16:22:02 -0400 Subject: [style] autopep8 --- src/leap/bitmask/app.py | 1 + src/leap/bitmask/services/eip/darwinvpnlauncher.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 31dba157..e5189e23 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -76,6 +76,7 @@ codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) import psutil + def qt_hack_ubuntu(): """Export two env vars to avoid gui corruption, see #8028""" os.environ['QT_GRAPHICSSYSTEM'] = 'native' diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index 94161192..e697b118 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -112,7 +112,8 @@ class DarwinVPNLauncher(VPNLauncher): :returns: True if kext is loaded, False otherwise. :rtype: bool """ - loaded = bool(commands.getoutput('kextstat | grep "net.sf.tuntaposx.tun"')) + loaded = bool(commands.getoutput( + 'kextstat | grep "net.sf.tuntaposx.tun"')) if not loaded: logger.error("tuntaposx extension not loaded!") return loaded -- cgit v1.2.3 From 60aed8f499f6581e6568c6f66090da1aa8ac2fe0 Mon Sep 17 00:00:00 2001 From: Paixu Aabuizia Date: Wed, 3 Feb 2016 21:40:30 +0100 Subject: [bug] vindows initializer does not return state causing the application to always quit - log the init_platform failure to the critical log - return True/False in initializer - remove implementation to install driver and display informative message instead --- src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/platform_init/initializers.py | 39 ++++++++------------------ 2 files changed, 12 insertions(+), 28 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 20909038..cde44f7b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -302,6 +302,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._mail_conductor.connect_mail_signals(self._mail_status) if not init_platform(): + logger.critical('init_platform failed, quitting application.') self.quit() return diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index e8d48e4a..751762df 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -257,36 +257,19 @@ def WindowsInitializer(): if not _windows_has_tap_device(): msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("TAP Driver")) - msg.setText(msg.tr("Bitmask needs to install the necessary drivers " - "for Encrypted Internet to work. Would you like to " - "proceed?")) + msg.setText(msg.tr("Bitmask needs a TAP Driver installed " + "for Encrypted Internet to work. Please reinstall " + "bitmask-client to proceed.")) msg.setInformativeText(msg.tr("Encrypted Internet uses VPN, which " "needs a TAP device installed and none " - "has been found. This will ask for " - "administrative privileges.")) - msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) - msg.setDefaultButton(QtGui.QMessageBox.Yes) - ret = msg.exec_() - - if ret == QtGui.QMessageBox.Yes: - # XXX should do this only if executed inside bundle. - # Let's assume it's the only way it's gonna be executed under win - # by now. - driver_path = os.path.join(os.getcwd(), - "apps", - "eip", - "tap_driver") - dev_installer = os.path.join(driver_path, - "devcon.exe") - if os.path.isfile(dev_installer) and \ - stat.S_IXUSR & os.stat(dev_installer)[stat.ST_MODE] != 0: - inf_path = os.path.join(driver_path, - "OemWin2k.inf") - cmd = [dev_installer, "install", inf_path, "tap0901"] - ret = subprocess.call(cmd, stdout=subprocess.PIPE, shell=True) - else: - logger.error("Tried to install TAP driver, but the installer " - "is not found or not executable") + "has been found. The bitmask-client " + "installer prompts for installing the " + "TAP Driver.")) + msg.setStandardButtons(QtGui.QMessageBox.Ok) + logger.error('TAP Drivers not installed') + msg.exec_() + return False + return True # # Darwin initializer functions -- cgit v1.2.3 From 6fd1c73db49c5e1e08cf7963017470511fef0059 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 11:44:41 -0400 Subject: [feature] add bitmask_cli entrypoint --- src/leap/bitmask/cli/__init__.py | 0 src/leap/bitmask/cli/bitmask_cli.py | 341 ++++++++++++++++++++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 src/leap/bitmask/cli/__init__.py create mode 100755 src/leap/bitmask/cli/bitmask_cli.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/cli/__init__.py b/src/leap/bitmask/cli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/cli/bitmask_cli.py b/src/leap/bitmask/cli/bitmask_cli.py new file mode 100755 index 00000000..c5bb1b15 --- /dev/null +++ b/src/leap/bitmask/cli/bitmask_cli.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# bitmask_cli +# Copyright (C) 2015, 2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Bitmask Command Line interface: zmq client. +""" +import json +import sys +import getpass +import argparse + +from colorama import init as color_init +from colorama import Fore +from twisted.internet import reactor +from txzmq import ZmqEndpoint, ZmqEndpointType +from txzmq import ZmqFactory, ZmqREQConnection +from txzmq import ZmqRequestTimeoutError + +from leap.bitmask.core import ENDPOINT + + +class BitmaskCLI(object): + + def __init__(self): + parser = argparse.ArgumentParser( + usage='''bitmask_cli [] + +Controls the Bitmask application. + +SERVICE COMMANDS: + + user Handles Bitmask accounts + mail Bitmask Encrypted Mail + eip Encrypted Internet Proxy + keys Bitmask Keymanager + +GENERAL COMMANDS: + + version prints version number and exit + launch launch the Bitmask backend daemon + shutdown shutdown Bitmask backend daemon + status displays general status about the running Bitmask services + debug show some debug info about bitmask-core + + +''', epilog=("Use 'bitmask_cli --help' to learn more " + "about each command.")) + parser.add_argument('command', help='Subcommand to run') + + # parse_args defaults to [1:] for args, but you need to + # exclude the rest of the args too, or validation will fail + args = parser.parse_args(sys.argv[1:2]) + self.args = args + self.subargs = None + + if not hasattr(self, args.command): + print 'Unrecognized command' + parser.print_help() + exit(1) + + # use dispatch pattern to invoke method with same name + getattr(self, args.command)() + + def user(self): + parser = argparse.ArgumentParser( + description=('Handles Bitmask accounts: creation, authentication ' + 'and modification'), + prog='bitmask_cli user') + parser.add_argument('username', nargs='?', + help='username ID, in the form ') + parser.add_argument('--create', action='store_true', + help='register a new user, if possible') + parser.add_argument('--authenticate', action='store_true', + help='logs in against the provider') + parser.add_argument('--logout', action='store_true', + help='ends any active session with the provider') + parser.add_argument('--active', action='store_true', + help='shows the active user, if any') + # now that we're inside a subcommand, ignore the first + # TWO argvs, ie the command (bitmask_cli) and the subcommand (user) + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + def mail(self): + parser = argparse.ArgumentParser( + description='Bitmask Encrypted Mail service', + prog='bitmask_cli mail') + parser.add_argument('--start', action='store_true', + help='tries to start the mail service') + parser.add_argument('--stop', action='store_true', + help='stops the mail service if running') + parser.add_argument('--status', action='store_true', + help='displays status about the mail service') + parser.add_argument('--enable', action='store_true') + parser.add_argument('--disable', action='store_true') + parser.add_argument('--get-imap-token', action='store_true', + help='returns token for the IMAP service') + parser.add_argument('--get-smtp-token', action='store_true', + help='returns token for the SMTP service') + parser.add_argument('--get-smtp-certificate', action='store_true', + help='downloads a new smtp certificate') + parser.add_argument('--check-smtp-certificate', action='store_true', + help='downloads a new smtp certificate ' + '(NOT IMPLEMENTED)') + + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + def eip(self): + parser = argparse.ArgumentParser( + description='Encrypted Internet Proxy service', + prog='bitmask_cli eip') + parser.add_argument('--start', action='store_true', + help='Start service') + parser.add_argument('--stop', action='store_true', help='Stop service') + parser.add_argument('--status', action='store_true', + help='Display status about service') + parser.add_argument('--enable', action='store_true') + parser.add_argument('--disable', action='store_true') + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + def keys(self): + parser = argparse.ArgumentParser( + description='Bitmask Keymanager management service', + prog='bitmask_cli keys') + parser.add_argument('--status', action='store_true', + help='Display status about service') + parser.add_argument('--list-keys', action='store_true', + help='List all known keys') + parser.add_argument('--export-key', action='store_true', + help='Export the given key') + args = parser.parse_args(sys.argv[2:]) + self.subargs = args + + # Single commands + + def launch(self): + pass + + def shutdown(self): + pass + + def status(self): + pass + + def version(self): + pass + + def debug(self): + pass + + +def get_zmq_connection(): + zf = ZmqFactory() + e = ZmqEndpoint(ZmqEndpointType.connect, ENDPOINT) + return ZmqREQConnection(zf, e) + + +def error(msg, stop=False): + print Fore.RED + "[!] %s" % msg + Fore.RESET + if stop: + reactor.stop() + else: + sys.exit(1) + + +def timeout_handler(failure, stop_reactor=True): + # TODO ---- could try to launch the bitmask daemon here and retry + + if failure.trap(ZmqRequestTimeoutError) == ZmqRequestTimeoutError: + print (Fore.RED + "[ERROR] Timeout contacting the bitmask daemon. " + "Is it running?" + Fore.RESET) + reactor.stop() + + +def do_print_result(stuff): + obj = json.loads(stuff[0]) + if not obj['error']: + print Fore.GREEN + '%s' % obj['result'] + Fore.RESET + else: + print Fore.RED + 'ERROR:' + '%s' % obj['error'] + Fore.RESET + + +def send_command(cli): + + args = cli.args + subargs = cli.subargs + cb = do_print_result + + cmd = args.command + + if cmd == 'launch': + # XXX careful! Should see if the process in PID is running, + # avoid launching again. + import commands + commands.getoutput('bitmaskd') + reactor.stop() + return + + elif cmd == 'version': + do_print_result([json.dumps( + {'result': 'bitmask_cli: 0.0.1', + 'error': None})]) + data = ('version',) + + elif cmd == 'status': + data = ('status',) + + elif cmd == 'shutdown': + data = ('shutdown',) + + elif cmd == 'debug': + data = ('stats',) + + elif cmd == 'user': + username = subargs.username + if username and '@' not in username: + error("Username ID must be in the form ", + stop=True) + return + if not username: + username = '' + + # TODO check that ONLY ONE FLAG is True + # TODO check that AT LEAST ONE FLAG is True + + passwd = getpass.getpass() + data = ['user'] + + if subargs.active: + data += ['active', '', ''] + + elif subargs.create: + data += ['signup', username, passwd] + + elif subargs.authenticate: + data += ['authenticate', username, passwd] + + elif subargs.logout: + data += ['logout', username, passwd] + + else: + error('Use bitmask_cli user --help to see available subcommands') + return + + elif cmd == 'mail': + data = ['mail'] + + if subargs.status: + data += ['status'] + + elif subargs.enable: + data += ['enable'] + + elif subargs.disable: + data += ['disable'] + + elif subargs.get_imap_token: + data += ['get_imap_token'] + + elif subargs.get_smtp_token: + data += ['get_smtp_token'] + + elif subargs.get_smtp_certificate: + data += ['get_smtp_certificate'] + + else: + error('Use bitmask_cli mail --help to see available subcommands') + return + + elif cmd == 'eip': + data = ['eip'] + + if subargs.status: + data += ['status'] + + elif subargs.enable: + data += ['enable'] + + elif subargs.disable: + data += ['disable'] + + elif subargs.start: + data += ['start'] + + elif subargs.stop: + data += ['stop'] + + else: + error('Use bitmask_cli eip --help to see available subcommands', + stop=True) + return + + elif cmd == 'keys': + data = ['keys'] + + if subargs.status: + data += ['status'] + + elif subargs.list_keys: + data += ['list_keys'] + + elif subargs.export_key: + data += ['export_keys'] + + else: + error('Use bitmask_cli keys --help to see available subcommands', + stop=True) + return + + s = get_zmq_connection() + + d = s.sendMsg(*data, timeout=20) + d.addCallback(cb) + d.addCallback(lambda x: reactor.stop()) + d.addErrback(timeout_handler) + + +def main(): + color_init() + cli = BitmaskCLI() + reactor.callWhenRunning(reactor.callLater, 0, send_command, cli) + reactor.run() + +if __name__ == "__main__": + main() -- cgit v1.2.3 From f4503842fdc288fb6336211099ad0a8d01165df3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Mar 2016 12:02:26 -0400 Subject: [feature] landing of bitmask.core --- src/leap/bitmask/core/__init__.py | 2 + src/leap/bitmask/core/_zmq.py | 68 ++++ src/leap/bitmask/core/bitmaskd.tac | 11 + src/leap/bitmask/core/configurable.py | 262 +++++++++++++ src/leap/bitmask/core/dispatcher.py | 196 ++++++++++ src/leap/bitmask/core/launcher.py | 37 ++ src/leap/bitmask/core/mail_services.py | 672 +++++++++++++++++++++++++++++++++ src/leap/bitmask/core/service.py | 174 +++++++++ src/leap/bitmask/core/uuid_map.py | 115 ++++++ src/leap/bitmask/core/web/__init__.py | 0 src/leap/bitmask/core/web/index.html | 70 ++++ src/leap/bitmask/core/web/root.py | 0 src/leap/bitmask/core/websocket.py | 98 +++++ 13 files changed, 1705 insertions(+) create mode 100644 src/leap/bitmask/core/__init__.py create mode 100644 src/leap/bitmask/core/_zmq.py create mode 100644 src/leap/bitmask/core/bitmaskd.tac create mode 100644 src/leap/bitmask/core/configurable.py create mode 100644 src/leap/bitmask/core/dispatcher.py create mode 100644 src/leap/bitmask/core/launcher.py create mode 100644 src/leap/bitmask/core/mail_services.py create mode 100644 src/leap/bitmask/core/service.py create mode 100644 src/leap/bitmask/core/uuid_map.py create mode 100644 src/leap/bitmask/core/web/__init__.py create mode 100644 src/leap/bitmask/core/web/index.html create mode 100644 src/leap/bitmask/core/web/root.py create mode 100644 src/leap/bitmask/core/websocket.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/core/__init__.py b/src/leap/bitmask/core/__init__.py new file mode 100644 index 00000000..bda4b8d0 --- /dev/null +++ b/src/leap/bitmask/core/__init__.py @@ -0,0 +1,2 @@ +APPNAME = "bitmask.core" +ENDPOINT = "ipc:///tmp/%s.sock" % APPNAME diff --git a/src/leap/bitmask/core/_zmq.py b/src/leap/bitmask/core/_zmq.py new file mode 100644 index 00000000..a656fc65 --- /dev/null +++ b/src/leap/bitmask/core/_zmq.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# _zmq.py +# Copyright (C) 2015 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +ZMQ REQ-REP Dispatcher. +""" + +from twisted.application import service +from twisted.internet import reactor +from twisted.python import log + +from txzmq import ZmqEndpoint, ZmqEndpointType +from txzmq import ZmqFactory, ZmqREPConnection + +from leap.bitmask.core import ENDPOINT +from leap.bitmask.core.dispatcher import CommandDispatcher + + +class ZMQServerService(service.Service): + + def __init__(self, core): + self._core = core + + def startService(self): + zf = ZmqFactory() + e = ZmqEndpoint(ZmqEndpointType.bind, ENDPOINT) + + self._conn = _DispatcherREPConnection(zf, e, self._core) + reactor.callWhenRunning(self._conn.do_greet) + service.Service.startService(self) + + def stopService(self): + service.Service.stopService(self) + + +class _DispatcherREPConnection(ZmqREPConnection): + + def __init__(self, zf, e, core): + ZmqREPConnection.__init__(self, zf, e) + self.dispatcher = CommandDispatcher(core) + + def gotMessage(self, msgId, *parts): + + r = self.dispatcher.dispatch(parts) + r.addCallback(self.defer_reply, msgId) + + def defer_reply(self, response, msgId): + reactor.callLater(0, self.reply, msgId, str(response)) + + def log_err(self, failure, msgId): + log.err(failure) + self.defer_reply("ERROR: %r" % failure, msgId) + + def do_greet(self): + log.msg('starting ZMQ dispatcher') diff --git a/src/leap/bitmask/core/bitmaskd.tac b/src/leap/bitmask/core/bitmaskd.tac new file mode 100644 index 00000000..3c9b1d8b --- /dev/null +++ b/src/leap/bitmask/core/bitmaskd.tac @@ -0,0 +1,11 @@ +# Service composition for bitmask-core. +# Run as: twistd -n -y bitmaskd.tac +# +from twisted.application import service + +from leap.bitmask.core.service import BitmaskBackend + + +bb = BitmaskBackend() +application = service.Application("bitmaskd") +bb.setServiceParent(application) diff --git a/src/leap/bitmask/core/configurable.py b/src/leap/bitmask/core/configurable.py new file mode 100644 index 00000000..3b97916d --- /dev/null +++ b/src/leap/bitmask/core/configurable.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- +# configurable.py +# Copyright (C) 2015, 2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Configurable Backend for Bitmask Service. +""" +import ConfigParser +import locale +import os +import re +import sys + +from twisted.application import service +from twisted.python import log + +from leap.common import files + + +class MissingConfigEntry(Exception): + """ + A required config entry was not found. + """ + + +class ConfigurableService(service.MultiService): + + config_file = u"bitmaskd.cfg" + service_names = ('mail', 'eip', 'zmq', 'web') + + def __init__(self, basedir='~/.config/leap'): + service.MultiService.__init__(self) + + path = os.path.abspath(os.path.expanduser(basedir)) + if not os.path.isdir(path): + files.mkdir_p(path) + self.basedir = path + + # creates self.config + self.read_config() + + def get_config(self, section, option, default=None, boolean=False): + try: + if boolean: + return self.config.getboolean(section, option) + + item = self.config.get(section, option) + return item + + except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): + if default is None: + fn = os.path.join(self.basedir, self.config_file) + raise MissingConfigEntry("%s is missing the [%s]%s entry" + % (_quote_output(fn), + section, option)) + return default + + def set_config(self, section, option, value): + if not self.config.has_section(section): + self.config.add_section(section) + self.config.set(section, option, value) + self.save_config() + self.read_config() + assert self.config.get(section, option) == value + + def read_config(self): + self.config = ConfigParser.SafeConfigParser() + bitmaskd_cfg = self._get_config_path() + + if not os.path.isfile(bitmaskd_cfg): + self._create_default_config(bitmaskd_cfg) + + try: + with open(bitmaskd_cfg, "rb") as f: + self.config.readfp(f) + except EnvironmentError: + if os.path.exists(bitmaskd_cfg): + raise + + def save_config(self): + bitmaskd_cfg = self._get_config_path() + with open(bitmaskd_cfg, 'wb') as f: + self.config.write(f) + + def _create_default_config(self, path): + with open(path, 'w') as outf: + outf.write(DEFAULT_CONFIG) + + def _get_config_path(self): + return os.path.join(self.basedir, self.config_file) + + +DEFAULT_CONFIG = """ +[services] +mail = True +eip = True +zmq = True +web = False +""" + + +def canonical_encoding(encoding): + if encoding is None: + log.msg("Warning: falling back to UTF-8 encoding.", level=log.WEIRD) + encoding = 'utf-8' + encoding = encoding.lower() + if encoding == "cp65001": + encoding = 'utf-8' + elif (encoding == "us-ascii" or encoding == "646" or encoding == + "ansi_x3.4-1968"): + encoding = 'ascii' + + return encoding + + +def check_encoding(encoding): + # sometimes Python returns an encoding name that it doesn't support for + # conversion fail early if this happens + try: + u"test".encode(encoding) + except (LookupError, AttributeError): + raise AssertionError( + "The character encoding '%s' is not supported for conversion." % ( + encoding,)) + +filesystem_encoding = None +io_encoding = None +is_unicode_platform = False + + +def _reload(): + global filesystem_encoding, io_encoding, is_unicode_platform + + filesystem_encoding = canonical_encoding(sys.getfilesystemencoding()) + check_encoding(filesystem_encoding) + + if sys.platform == 'win32': + # On Windows we install UTF-8 stream wrappers for sys.stdout and + # sys.stderr, and reencode the arguments as UTF-8 (see + # scripts/runner.py). + io_encoding = 'utf-8' + else: + ioenc = None + if hasattr(sys.stdout, 'encoding'): + ioenc = sys.stdout.encoding + if ioenc is None: + try: + ioenc = locale.getpreferredencoding() + except Exception: + pass # work around + io_encoding = canonical_encoding(ioenc) + + check_encoding(io_encoding) + + is_unicode_platform = sys.platform in ["win32", "darwin"] + +_reload() + + +def _quote_output(s, quotemarks=True, quote_newlines=None, encoding=None): + """ + Encode either a Unicode string or a UTF-8-encoded bytestring for + representation on stdout or stderr, tolerating errors. If 'quotemarks' is + True, the string is always quoted; otherwise, it is quoted only if + necessary to avoid ambiguity or control bytes in the output. (Newlines are + counted as control bytes iff quote_newlines is True.) + + Quoting may use either single or double quotes. Within single quotes, all + characters stand for themselves, and ' will not appear. Within double + quotes, Python-compatible backslash escaping is used. + + If not explicitly given, quote_newlines is True when quotemarks is True. + """ + assert isinstance(s, (str, unicode)) + if quote_newlines is None: + quote_newlines = quotemarks + + if isinstance(s, str): + try: + s = s.decode('utf-8') + except UnicodeDecodeError: + return 'b"%s"' % ( + ESCAPABLE_8BIT.sub( + lambda m: _str_escape(m, quote_newlines), s),) + + must_double_quote = (quote_newlines and MUST_DOUBLE_QUOTE_NL or + MUST_DOUBLE_QUOTE) + if must_double_quote.search(s) is None: + try: + out = s.encode(encoding or io_encoding) + if quotemarks or out.startswith('"'): + return "'%s'" % (out,) + else: + return out + except (UnicodeDecodeError, UnicodeEncodeError): + pass + + escaped = ESCAPABLE_UNICODE.sub( + lambda m: _unicode_escape(m, quote_newlines), s) + return '"%s"' % ( + escaped.encode(encoding or io_encoding, 'backslashreplace'),) + + +def _unicode_escape(m, quote_newlines): + u = m.group(0) + if u == u'"' or u == u'$' or u == u'`' or u == u'\\': + return u'\\' + u + elif u == u'\n' and not quote_newlines: + return u + if len(u) == 2: + codepoint = ( + ord(u[0]) - 0xD800) * 0x400 + ord(u[1]) - 0xDC00 + 0x10000 + else: + codepoint = ord(u) + if codepoint > 0xFFFF: + return u'\\U%08x' % (codepoint,) + elif codepoint > 0xFF: + return u'\\u%04x' % (codepoint,) + else: + return u'\\x%02x' % (codepoint,) + + +def _str_escape(m, quote_newlines): + c = m.group(0) + if c == '"' or c == '$' or c == '`' or c == '\\': + return '\\' + c + elif c == '\n' and not quote_newlines: + return c + else: + return '\\x%02x' % (ord(c),) + +MUST_DOUBLE_QUOTE_NL = re.compile( + ur'[^\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]', + re.DOTALL) +MUST_DOUBLE_QUOTE = re.compile( + ur'[^\n\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]', + re.DOTALL) + +ESCAPABLE_8BIT = re.compile( + r'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E]', + re.DOTALL) + +# if we must double-quote, then we have to escape ", $ and `, but need not +# escape ' + +ESCAPABLE_UNICODE = re.compile( + ur'([\uD800-\uDBFF][\uDC00-\uDFFF])|' # valid surrogate pairs + ur'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E\u00A0-\uD7FF' + ur'\uE000-\uFDCF\uFDF0-\uFFFC]', + re.DOTALL) diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py new file mode 100644 index 00000000..4d7e1813 --- /dev/null +++ b/src/leap/bitmask/core/dispatcher.py @@ -0,0 +1,196 @@ +# -*- coding: utf-8 -*- +# dispatcher.py +# Copyright (C) 2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Command dispatcher. +""" +import json + +from twisted.internet import defer +from twisted.python import failure, log + + +# TODO implement sub-classes to dispatch subcommands (user, mail). + + +class CommandDispatcher(object): + + def __init__(self, core): + + self.core = core + + def _get_service(self, name): + + try: + return self.core.getServiceNamed(name) + except KeyError: + return None + + def dispatch(self, msg): + cmd = msg[0] + + _method = getattr(self, 'do_' + cmd.upper(), None) + + if not _method: + return defer.fail(failure.Failure(RuntimeError('No such command'))) + + return defer.maybeDeferred(_method, *msg) + + def do_STATS(self, *parts): + return _format_result(self.core.do_stats()) + + def do_VERSION(self, *parts): + return _format_result(self.core.do_version()) + + def do_STATUS(self, *parts): + return _format_result(self.core.do_status()) + + def do_SHUTDOWN(self, *parts): + return _format_result(self.core.do_shutdown()) + + def do_USER(self, *parts): + + subcmd = parts[1] + user, password = parts[2], parts[3] + + bf = self._get_service('bonafide') + + if subcmd == 'authenticate': + d = bf.do_authenticate(user, password) + + elif subcmd == 'signup': + d = bf.do_signup(user, password) + + elif subcmd == 'logout': + d = bf.do_logout(user, password) + + elif subcmd == 'active': + d = bf.do_get_active_user() + + d.addCallbacks(_format_result, _format_error) + return d + + def do_EIP(self, *parts): + subcmd = parts[1] + eip_label = 'eip' + + if subcmd == 'enable': + return _format_result( + self.core.do_enable_service(eip_label)) + + eip = self._get_service(eip_label) + if not eip: + return _format_result('eip: disabled') + + if subcmd == 'status': + return _format_result(eip.do_status()) + + elif subcmd == 'disable': + return _format_result( + self.core.do_disable_service(eip_label)) + + elif subcmd == 'start': + # TODO --- attempt to get active provider + # TODO or catch the exception and send error + provider = parts[2] + d = eip.do_start(provider) + d.addCallbacks(_format_result, _format_error) + return d + + elif subcmd == 'stop': + d = eip.do_stop() + d.addCallbacks(_format_result, _format_error) + return d + + def do_MAIL(self, *parts): + + subcmd = parts[1] + mail_label = 'mail' + + if subcmd == 'enable': + return _format_result( + self.core.do_enable_service(mail_label)) + + m = self._get_service(mail_label) + bf = self._get_service('bonafide') + + if not m: + return _format_result('mail: disabled') + + if subcmd == 'status': + return _format_result(m.do_status()) + + elif subcmd == 'disable': + return _format_result(self.core.do_disable_service(mail_label)) + + elif subcmd == 'get_imap_token': + d = m.get_imap_token() + d.addCallbacks(_format_result, _format_error) + return d + + elif subcmd == 'get_smtp_token': + d = m.get_smtp_token() + d.addCallbacks(_format_result, _format_error) + return d + + elif subcmd == 'get_smtp_certificate': + # TODO move to mail service + # TODO should ask for confirmation? like --force or something, + # if we already have a valid one. or better just refuse if cert + # exists. + # TODO how should we pass the userid?? + # - Keep an 'active' user in bonafide (last authenticated) + # (doing it now) + # - Get active user from Mail Service (maybe preferred?) + # - Have a command/method to set 'active' user. + + @defer.inlineCallbacks + def save_cert(cert_data): + userid, cert_str = cert_data + cert_path = yield m.do_get_smtp_cert_path(userid) + with open(cert_path, 'w') as outf: + outf.write(cert_str) + defer.returnValue('certificate saved to %s' % cert_path) + + d = bf.do_get_smtp_cert() + d.addCallback(save_cert) + d.addCallbacks(_format_result, _format_error) + return d + + def do_KEYS(self, *parts): + subcmd = parts[1] + + keymanager_label = 'keymanager' + km = self._get_service(keymanager_label) + bf = self._get_service('bonafide') + + if not km: + return _format_result('keymanager: disabled') + + if subcmd == 'list_keys': + d = bf.do_get_active_user() + d.addCallback(km.do_list_keys) + d.addCallbacks(_format_result, _format_error) + return d + + +def _format_result(result): + return json.dumps({'error': None, 'result': result}) + + +def _format_error(failure): + log.err(failure) + return json.dumps({'error': failure.value.message, 'result': None}) diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py new file mode 100644 index 00000000..7d658017 --- /dev/null +++ b/src/leap/bitmask/core/launcher.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# launcher.py +# Copyright (C) 2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Run bitmask daemon. +""" +from twisted.scripts.twistd import run +from os.path import join, dirname +from sys import argv + +from leap.bitmask import core + + +def run_bitmaskd(): + # TODO --- configure where to put the logs... (get --logfile, --logdir + # from the bitmask_cli + argv[1:] = [ + '-y', join(dirname(core.__file__), "bitmaskd.tac"), + '--pidfile', '/tmp/bitmaskd.pid', + '--logfile', '/tmp/bitmaskd.log', + '--umask=0022', + ] + print '[+] launching bitmaskd...' + run() diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py new file mode 100644 index 00000000..9858d498 --- /dev/null +++ b/src/leap/bitmask/core/mail_services.py @@ -0,0 +1,672 @@ +""" +Mail services. + +This is quite moving work still. +This should be moved to the different packages when it stabilizes. +""" +import json +import os +from glob import glob +from collections import defaultdict +from collections import namedtuple + +from twisted.application import service +from twisted.internet import defer +from twisted.python import log + +from leap.bonafide import config +from leap.common.service_hooks import HookableService +from leap.keymanager import KeyManager, openpgp +from leap.keymanager.errors import KeyNotFound +from leap.soledad.client.api import Soledad +from leap.mail.constants import INBOX_NAME +from leap.mail.mail import Account +from leap.mail.imap.service import imap +from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD +from leap.mail import smtp + +from leap.bitmask.core.uuid_map import UserMap + + +class Container(object): + + def __init__(self): + self._instances = defaultdict(None) + + def get_instance(self, key): + return self._instances.get(key, None) + + +class ImproperlyConfigured(Exception): + pass + + +def get_all_soledad_uuids(): + return [os.path.split(p)[-1].split('.db')[0] for p in + glob(os.path.expanduser('~/.config/leap/soledad/*.db'))] + # FIXME do not hardcode basedir + + +class SoledadContainer(Container): + + def __init__(self, basedir='~/.config/leap'): + # FIXME do not hardcode basedir + self._basedir = os.path.expanduser(basedir) + self._usermap = UserMap() + super(SoledadContainer, self).__init__() + + def add_instance(self, userid, passphrase, uuid=None, token=None): + + if not uuid: + bootstrapped_uuid = self._usermap.lookup_uuid(userid, passphrase) + uuid = bootstrapped_uuid + if not uuid: + return + else: + self._usermap.add(userid, uuid, passphrase) + + user, provider = userid.split('@') + + soledad_path = os.path.join(self._basedir, 'soledad') + soledad_url = _get_soledad_uri(self._basedir, provider) + cert_path = _get_ca_cert_path(self._basedir, provider) + + soledad = self._create_soledad_instance( + uuid, passphrase, soledad_path, soledad_url, + cert_path, token) + + self._instances[userid] = soledad + + data = {'user': userid, 'uuid': uuid, 'token': token, + 'soledad': soledad} + self.service.trigger_hook('on_new_soledad_instance', **data) + + def _create_soledad_instance(self, uuid, passphrase, basedir, server_url, + cert_file, token): + # setup soledad info + secrets_path = os.path.join( + basedir, '%s.secret' % uuid) + local_db_path = os.path.join( + basedir, '%s.db' % uuid) + + if token is None: + syncable = False + token = '' + else: + syncable = True + + return Soledad( + uuid, + unicode(passphrase), + secrets_path=secrets_path, + local_db_path=local_db_path, + server_url=server_url, + cert_file=cert_file, + auth_token=token, + defer_encryption=True, + syncable=syncable) + + def set_remote_auth_token(self, userid, token): + self.get_instance(userid).token = token + + def set_syncable(self, userid, state): + # TODO should check that there's a token! + self.get_instance(userid).set_syncable(bool(state)) + + def sync(self, userid): + self.get_instance(userid).sync() + + +def _get_provider_from_full_userid(userid): + _, provider_id = config.get_username_and_provider(userid) + return config.Provider(provider_id) + + +def is_service_ready(service, provider): + """ + Returns True when the following conditions are met: + - Provider offers that service. + - We have the config files for the service. + - The service is enabled. + """ + has_service = provider.offers_service(service) + has_config = provider.has_config_for_service(service) + is_enabled = provider.is_service_enabled(service) + return has_service and has_config and is_enabled + + +class SoledadService(service.Service, HookableService): + + subscribed_to_hooks = ('on_bonafide_auth', 'on_passphrase_entry') + + def __init__(self, basedir): + service.Service.__init__(self) + self._basedir = basedir + + def startService(self): + log.msg('Starting Soledad Service') + self._container = SoledadContainer() + self._container.service = self + super(SoledadService, self).startService() + + # hooks + + def hook_on_passphrase_entry(self, **kw): + userid = kw.get('username') + provider = _get_provider_from_full_userid(userid) + provider.callWhenReady(self._hook_on_passphrase_entry, provider, **kw) + + def _hook_on_passphrase_entry(self, provider, **kw): + if is_service_ready('mx', provider): + userid = kw.get('username') + password = kw.get('password') + uuid = kw.get('uuid') + container = self._container + log.msg("on_passphrase_entry: New Soledad Instance: %s" % userid) + if not container.get_instance(userid): + container.add_instance(userid, password, uuid=uuid, token=None) + else: + log.msg('Service MX is not ready...') + + def hook_on_bonafide_auth(self, **kw): + userid = kw['username'] + provider = _get_provider_from_full_userid(userid) + provider.callWhenReady(self._hook_on_bonafide_auth, provider, **kw) + + def _hook_on_bonafide_auth(self, provider, **kw): + if provider.offers_service('mx'): + userid = kw['username'] + password = kw['password'] + token = kw['token'] + uuid = kw['uuid'] + + container = self._container + if container.get_instance(userid): + log.msg("Passing a new SRP Token to Soledad: %s" % userid) + container.set_remote_auth_token(userid, token) + container.set_syncable(userid, True) + else: + log.msg("Adding a new Soledad Instance: %s" % userid) + container.add_instance( + userid, password, uuid=uuid, token=token) + + +class KeymanagerContainer(Container): + + def __init__(self, basedir): + self._basedir = os.path.expanduser(basedir) + super(KeymanagerContainer, self).__init__() + + def add_instance(self, userid, token, uuid, soledad): + + keymanager = self._create_keymanager_instance( + userid, token, uuid, soledad) + + d = self._get_or_generate_keys(keymanager, userid) + d.addCallback(self._on_keymanager_ready_cb, userid, soledad) + return d + + def set_remote_auth_token(self, userid, token): + self.get_instance(userid)._token = token + + def _on_keymanager_ready_cb(self, keymanager, userid, soledad): + # TODO use onready-deferreds instead + self._instances[userid] = keymanager + + log.msg("Adding Keymanager instance for: %s" % userid) + data = {'userid': userid, 'soledad': soledad, 'keymanager': keymanager} + self.service.trigger_hook('on_new_keymanager_instance', **data) + + def _get_or_generate_keys(self, keymanager, userid): + + def if_not_found_generate(failure): + # TODO -------------- should ONLY generate if INITIAL_SYNC_DONE. + # ie: put callback on_soledad_first_sync_ready ----------------- + # -------------------------------------------------------------- + failure.trap(KeyNotFound) + log.msg("Core: Key not found. Generating key for %s" % (userid,)) + d = keymanager.gen_key(openpgp.OpenPGPKey) + d.addCallbacks(send_key, log_key_error("generating")) + return d + + def send_key(ignored): + # ---------------------------------------------------------------- + # It might be the case that we have generated a key-pair + # but this hasn't been successfully uploaded. How do we know that? + # XXX Should this be a method of bonafide instead? + # ----------------------------------------------------------------- + d = keymanager.send_key(openpgp.OpenPGPKey) + d.addCallbacks( + lambda _: log.msg( + "Key generated successfully for %s" % userid), + log_key_error("sending")) + return d + + def log_key_error(step): + def log_error(failure): + log.err("Error while %s key!" % step) + log.err(failure) + return failure + return log_error + + d = keymanager.get_key( + userid, openpgp.OpenPGPKey, private=True, fetch_remote=False) + d.addErrback(if_not_found_generate) + d.addCallback(lambda _: keymanager) + return d + + def _create_keymanager_instance(self, userid, token, uuid, soledad): + user, provider = userid.split('@') + nickserver_uri = self._get_nicknym_uri(provider) + + cert_path = _get_ca_cert_path(self._basedir, provider) + api_uri = self._get_api_uri(provider) + + if not token: + token = self.service.tokens.get(userid) + + km_args = (userid, nickserver_uri, soledad) + km_kwargs = { + "token": token, "uid": uuid, + "api_uri": api_uri, "api_version": "1", + "ca_cert_path": cert_path, + "gpgbinary": "/usr/bin/gpg" + } + keymanager = KeyManager(*km_args, **km_kwargs) + return keymanager + + def _get_api_uri(self, provider): + # TODO get this from service.json (use bonafide service) + api_uri = "https://api.{provider}:4430".format( + provider=provider) + return api_uri + + def _get_nicknym_uri(self, provider): + return 'https://nicknym.{provider}:6425'.format( + provider=provider) + + +class KeymanagerService(service.Service, HookableService): + + subscribed_to_hooks = ('on_new_soledad_instance', 'on_bonafide_auth') + + def __init__(self, basedir='~/.config/leap'): + service.Service.__init__(self) + self._basedir = basedir + + def startService(self): + log.msg('Starting Keymanager Service') + self._container = KeymanagerContainer(self._basedir) + self._container.service = self + self.tokens = {} + super(KeymanagerService, self).startService() + + # hooks + + def hook_on_new_soledad_instance(self, **kw): + container = self._container + user = kw['user'] + token = kw['token'] + uuid = kw['uuid'] + soledad = kw['soledad'] + if not container.get_instance(user): + log.msg('Adding a new Keymanager instance for %s' % user) + if not token: + token = self.tokens.get(user) + container.add_instance(user, token, uuid, soledad) + + def hook_on_bonafide_auth(self, **kw): + userid = kw['username'] + provider = _get_provider_from_full_userid(userid) + provider.callWhenReady(self._hook_on_bonafide_auth, provider, **kw) + + def _hook_on_bonafide_auth(self, provider, **kw): + if provider.offers_service('mx'): + userid = kw['username'] + token = kw['token'] + + container = self._container + if container.get_instance(userid): + log.msg('Passing a new SRP Token to Keymanager: %s' % userid) + container.set_remote_auth_token(userid, token) + else: + log.msg('storing the keymanager token...') + self.tokens[userid] = token + + # commands + + def do_list_keys(self, userid): + km = self._container.get_instance(userid) + d = km.get_all_keys() + d.addCallback( + lambda keys: [ + (key.uids, key.fingerprint) for key in keys]) + return d + + +class StandardMailService(service.MultiService, HookableService): + """ + A collection of Services. + + This is the parent service, that launches 3 different services that expose + Encrypted Mail Capabilities on specific ports: + + - SMTP service, on port 2013 + - IMAP service, on port 1984 + - The IncomingMail Service, which doesn't listen on any port, but + watches and processes the Incoming Queue and saves the processed mail + into the matching INBOX. + """ + + name = 'mail' + + # TODO factor out Mail Service to inside mail package. + + subscribed_to_hooks = ('on_new_keymanager_instance',) + + def __init__(self, basedir): + self._basedir = basedir + self._soledad_sessions = {} + self._keymanager_sessions = {} + self._sendmail_opts = {} + self._imap_tokens = {} + self._smtp_tokens = {} + self._active_user = None + super(StandardMailService, self).__init__() + self.initializeChildrenServices() + + def initializeChildrenServices(self): + self.addService(IMAPService(self._soledad_sessions)) + self.addService(IncomingMailService(self)) + self.addService(SMTPService( + self._soledad_sessions, self._keymanager_sessions, + self._sendmail_opts)) + + def startService(self): + log.msg('Starting Mail Service...') + super(StandardMailService, self).startService() + + def stopService(self): + super(StandardMailService, self).stopService() + + def startInstance(self, userid, soledad, keymanager): + username, provider = userid.split('@') + + self._soledad_sessions[userid] = soledad + self._keymanager_sessions[userid] = keymanager + + sendmail_opts = _get_sendmail_opts(self._basedir, provider, username) + self._sendmail_opts[userid] = sendmail_opts + + incoming = self.getServiceNamed('incoming_mail') + incoming.startInstance(userid) + + def registerIMAPToken(token): + self._imap_tokens[userid] = token + self._active_user = userid + return token + + def registerSMTPToken(token): + self._smtp_tokens[userid] = token + return token + + d = soledad.get_or_create_service_token('imap') + d.addCallback(registerIMAPToken) + d.addCallback( + lambda _: soledad.get_or_create_service_token('smtp')) + d.addCallback(registerSMTPToken) + return d + + def stopInstance(self): + pass + + # hooks + + def hook_on_new_keymanager_instance(self, **kw): + # XXX we can specify this as a waterfall, or just AND the two + # conditions. + userid = kw['userid'] + soledad = kw['soledad'] + keymanager = kw['keymanager'] + + # TODO --- only start instance if "autostart" is True. + self.startInstance(userid, soledad, keymanager) + + # commands + + def do_status(self): + return 'mail: %s' % 'running' if self.running else 'disabled' + + def get_imap_token(self): + active_user = self._active_user + if not active_user: + return defer.succeed('NO ACTIVE USER') + token = self._imap_tokens.get(active_user) + return defer.succeed("IMAP TOKEN (%s): %s" % (active_user, token)) + + def get_smtp_token(self): + active_user = self._active_user + if not active_user: + return defer.succeed('NO ACTIVE USER') + token = self._smtp_tokens.get(active_user) + return defer.succeed("SMTP TOKEN (%s): %s" % (active_user, token)) + + def do_get_smtp_cert_path(self, userid): + username, provider = userid.split('@') + return _get_smtp_client_cert_path(self._basedir, provider, username) + + # access to containers + + def get_soledad_session(self, userid): + return self._soledad_sessions.get(userid) + + def get_keymanager_session(self, userid): + return self._keymanager_sessions.get(userid) + + +class IMAPService(service.Service): + + name = 'imap' + + def __init__(self, soledad_sessions): + port, factory = imap.run_service(soledad_sessions) + + self._port = port + self._factory = factory + self._soledad_sessions = soledad_sessions + super(IMAPService, self).__init__() + + def startService(self): + log.msg('Starting IMAP Service') + super(IMAPService, self).startService() + + def stopService(self): + self._port.stopListening() + self._factory.doStop() + super(IMAPService, self).stopService() + + +class SMTPService(service.Service): + + name = 'smtp' + + def __init__(self, soledad_sessions, keymanager_sessions, sendmail_opts, + basedir='~/.config/leap'): + + self._basedir = os.path.expanduser(basedir) + port, factory = smtp.run_service( + soledad_sessions, keymanager_sessions, sendmail_opts) + self._port = port + self._factory = factory + self._soledad_sessions = soledad_sessions + self._keymanager_sessions = keymanager_sessions + self._sendmail_opts = sendmail_opts + super(SMTPService, self).__init__() + + def startService(self): + log.msg('Starting SMTP Service') + super(SMTPService, self).startService() + + def stopService(self): + # TODO cleanup all instances + super(SMTPService, self).stopService() + + +class IncomingMailService(service.Service): + + name = 'incoming_mail' + + def __init__(self, mail_service): + super(IncomingMailService, self).__init__() + self._mail = mail_service + self._instances = {} + + def startService(self): + log.msg('Starting IncomingMail Service') + super(IncomingMailService, self).startService() + + def stopService(self): + super(IncomingMailService, self).stopService() + + # Individual accounts + + # TODO IncomingMail *IS* already a service. + # I think we should better model the current Service + # as a startInstance inside a container, and get this + # multi-tenant service inside the leap.mail.incoming.service. + # ... or just simply make it a multiService and set per-user + # instances as Child of this parent. + + def startInstance(self, userid): + soledad = self._mail.get_soledad_session(userid) + keymanager = self._mail.get_keymanager_session(userid) + + log.msg('Starting Incoming Mail instance for %s' % userid) + self._start_incoming_mail_instance( + keymanager, soledad, userid) + + def stopInstance(self, userid): + # TODO toggle offline! + pass + + def _start_incoming_mail_instance(self, keymanager, soledad, + userid, start_sync=True): + + def setUpIncomingMail(inbox): + incoming_mail = IncomingMail( + keymanager, soledad, + inbox, userid, + check_period=INCOMING_CHECK_PERIOD) + return incoming_mail + + def registerInstance(incoming_instance): + self._instances[userid] = incoming_instance + if start_sync: + incoming_instance.startService() + + acc = Account(soledad) + d = acc.callWhenReady( + lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) + d.addCallback(setUpIncomingMail) + d.addCallback(registerInstance) + d.addErrback(log.err) + return d + +# -------------------------------------------------------------------- +# +# config utilities. should be moved to bonafide +# + +SERVICES = ('soledad', 'smtp', 'eip') + + +Provider = namedtuple( + 'Provider', ['hostname', 'ip_address', 'location', 'port']) + +SendmailOpts = namedtuple( + 'SendmailOpts', ['cert', 'key', 'hostname', 'port']) + + +def _get_ca_cert_path(basedir, provider): + path = os.path.join( + basedir, 'providers', provider, 'keys', 'ca', 'cacert.pem') + return path + + +def _get_sendmail_opts(basedir, provider, username): + cert = _get_smtp_client_cert_path(basedir, provider, username) + key = cert + prov = _get_provider_for_service('smtp', basedir, provider) + hostname = prov.hostname + port = prov.port + opts = SendmailOpts(cert, key, hostname, port) + return opts + + +def _get_smtp_client_cert_path(basedir, provider, username): + path = os.path.join( + basedir, 'providers', provider, 'keys', 'client', 'stmp_%s.pem' % + username) + return path + + +def _get_config_for_service(service, basedir, provider): + if service not in SERVICES: + raise ImproperlyConfigured('Tried to use an unknown service') + + config_path = os.path.join( + basedir, 'providers', provider, '%s-service.json' % service) + try: + with open(config_path) as config: + config = json.loads(config.read()) + except IOError: + # FIXME might be that the provider DOES NOT offer this service! + raise ImproperlyConfigured( + 'could not open config file %s' % config_path) + else: + return config + + +def first(xs): + return xs[0] + + +def _pick_server(config, strategy=first): + """ + Picks a server from a list of possible choices. + The service files have a . + This implementation just picks the FIRST available server. + """ + servers = config['hosts'].keys() + choice = config['hosts'][strategy(servers)] + return choice + + +def _get_subdict(d, keys): + return {key: d.get(key) for key in keys} + + +def _get_provider_for_service(service, basedir, provider): + + if service not in SERVICES: + raise ImproperlyConfigured('Tried to use an unknown service') + + config = _get_config_for_service(service, basedir, provider) + p = _pick_server(config) + attrs = _get_subdict(p, ('hostname', 'ip_address', 'location', 'port')) + provider = Provider(**attrs) + return provider + + +def _get_smtp_uri(basedir, provider): + prov = _get_provider_for_service('smtp', basedir, provider) + url = 'https://{hostname}:{port}'.format( + hostname=prov.hostname, port=prov.port) + return url + + +def _get_soledad_uri(basedir, provider): + prov = _get_provider_for_service('soledad', basedir, provider) + url = 'https://{hostname}:{port}'.format( + hostname=prov.hostname, port=prov.port) + return url diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py new file mode 100644 index 00000000..4c18ab5d --- /dev/null +++ b/src/leap/bitmask/core/service.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# service.py +# Copyright (C) 2015 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Bitmask-core Service. +""" +import resource + +from twisted.internet import reactor +from twisted.python import log + +from leap.bonafide.service import BonafideService + +from leap.bitmask.core import configurable +from leap.bitmask.core import mail_services +from leap.bitmask.core import _zmq +from leap.bitmask.core import websocket +from leap.bitmask.core._version import get_versions + +from leap.common.events import server as event_server +from leap.vpn import EIPService + + +class BitmaskBackend(configurable.ConfigurableService): + + def __init__(self, basedir='~/.config/leap'): + + configurable.ConfigurableService.__init__(self, basedir) + + def enabled(service): + return self.get_config('services', service, False, boolean=True) + + on_start = reactor.callWhenRunning + + on_start(self.init_events) + on_start(self.init_bonafide) + + if enabled('mail'): + on_start(self.init_soledad) + on_start(self.init_keymanager) + on_start(self.init_mail) + + if enabled('eip'): + on_start(self.init_eip) + + if enabled('zmq'): + on_start(self.init_zmq) + + if enabled('web'): + on_start(self.init_web) + + def init_events(self): + event_server.ensure_server() + + def init_bonafide(self): + bf = BonafideService(self.basedir) + bf.setName("bonafide") + bf.setServiceParent(self) + # TODO ---- these hooks should be activated only if + # (1) we have enabled that service + # (2) provider offers this service + bf.register_hook('on_passphrase_entry', listener='soledad') + bf.register_hook('on_bonafide_auth', listener='soledad') + bf.register_hook('on_bonafide_auth', listener='keymanager') + + def init_soledad(self): + service = mail_services.SoledadService + sol = self._maybe_start_service( + 'soledad', service, self.basedir) + if sol: + sol.register_hook( + 'on_new_soledad_instance', listener='keymanager') + + def init_keymanager(self): + service = mail_services.KeymanagerService + km = self._maybe_start_service( + 'keymanager', service, self.basedir) + if km: + km.register_hook('on_new_keymanager_instance', listener='mail') + + def init_mail(self): + service = mail_services.StandardMailService + self._maybe_start_service('mail', service, self.basedir) + + def init_eip(self): + self._maybe_start_service('eip', EIPService) + + def init_zmq(self): + zs = _zmq.ZMQServerService(self) + zs.setServiceParent(self) + + def init_web(self): + ws = websocket.WebSocketsDispatcherService(self) + ws.setServiceParent(self) + + def _maybe_start_service(self, label, klass, *args, **kw): + try: + self.getServiceNamed(label) + except KeyError: + service = klass(*args, **kw) + service.setName(label) + service.setServiceParent(self) + return service + + # General commands for the BitmaskBackend Core Service + + def do_stats(self): + log.msg('BitmaskCore Service STATS') + mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + return 'BitmaskCore: [Mem usage: %s KB]' % (mem / 1024) + + def do_status(self): + # we may want to make this tuple a class member + services = ('soledad', 'keymanager', 'mail', 'eip') + + status_messages = [] + for name in services: + status = 'stopped' + try: + if self.getServiceNamed(name).running: + status = "running" + except KeyError: + pass + status_messages.append("[{}: {}]".format(name, status)) + + return " ".join(status_messages) + + def do_version(self): + version = get_versions()['version'] + return 'BitmaskCore: %s' % version + + def do_shutdown(self): + self.stopService() + reactor.callLater(1, reactor.stop) + return 'shutting down...' + + def do_enable_service(self, service): + assert service in self.service_names + self.set_config('services', service, 'True') + + if service == 'mail': + self.init_soledad() + self.init_keymanager() + self.init_mail() + + elif service == 'eip': + self.init_eip() + + elif service == 'zmq': + self.init_zmq() + + elif service == 'web': + self.init_web() + + return 'ok' + + def do_disable_service(self, service): + assert service in self.service_names + # TODO -- should stop also? + self.set_config('services', service, 'False') + return 'ok' diff --git a/src/leap/bitmask/core/uuid_map.py b/src/leap/bitmask/core/uuid_map.py new file mode 100644 index 00000000..5edc7216 --- /dev/null +++ b/src/leap/bitmask/core/uuid_map.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# uuid_map.py +# Copyright (C) 2015,2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +UUID Map: a persistent mapping between user-ids and uuids. +""" + +import base64 +import os +import re + +import scrypt + +from leap.common.config import get_path_prefix + + +MAP_PATH = os.path.join(get_path_prefix(), 'leap', 'uuids') + + +class UserMap(object): + + """ + A persistent mapping between user-ids and uuids. + """ + + # TODO Add padding to the encrypted string + + def __init__(self): + self._d = {} + self._lines = set([]) + if os.path.isfile(MAP_PATH): + self.load() + + def add(self, userid, uuid, passwd): + """ + Add a new userid-uuid mapping, and encrypt the record with the user + password. + """ + self._add_to_cache(userid, uuid) + self._lines.add(_encode_uuid_map(userid, uuid, passwd)) + self.dump() + + def _add_to_cache(self, userid, uuid): + self._d[userid] = uuid + + def load(self): + """ + Load a mapping from a default file. + """ + with open(MAP_PATH, 'r') as infile: + lines = infile.readlines() + self._lines = set(lines) + + def dump(self): + """ + Dump the mapping to a default file. + """ + with open(MAP_PATH, 'w') as out: + out.write('\n'.join(self._lines)) + + def lookup_uuid(self, userid, passwd=None): + """ + Lookup the uuid for a given userid. + + If no password is given, try to lookup on cache. + Else, try to decrypt all the records that we know about with the + passed password. + """ + if not passwd: + return self._d.get(userid) + + for line in self._lines: + guess = _decode_uuid_line(line, passwd) + if guess: + record_userid, uuid = guess + if record_userid == userid: + self._add_to_cache(userid, uuid) + return uuid + + def lookup_userid(self, uuid): + """ + Get the userid for the given uuid from cache. + """ + rev_d = {v: k for (k, v) in self._d.items()} + return rev_d.get(uuid) + + +def _encode_uuid_map(userid, uuid, passwd): + data = 'userid:%s:uuid:%s' % (userid, uuid) + encrypted = scrypt.encrypt(data, passwd, maxtime=0.05) + return base64.encodestring(encrypted).replace('\n', '') + + +def _decode_uuid_line(line, passwd): + decoded = base64.decodestring(line) + try: + maybe_decrypted = scrypt.decrypt(decoded, passwd, maxtime=0.1) + except scrypt.error: + return None + match = re.findall("userid\:(.+)\:uuid\:(.+)", maybe_decrypted) + if match: + return match[0] diff --git a/src/leap/bitmask/core/web/__init__.py b/src/leap/bitmask/core/web/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/core/web/index.html b/src/leap/bitmask/core/web/index.html new file mode 100644 index 00000000..9490eca8 --- /dev/null +++ b/src/leap/bitmask/core/web/index.html @@ -0,0 +1,70 @@ + + + + Bitmask WebSockets Endpoint + + + +

    Bitmask Control Panel

    + +
    +

    Command:

    +
    + +
    
    +   
    +
    diff --git a/src/leap/bitmask/core/web/root.py b/src/leap/bitmask/core/web/root.py
    new file mode 100644
    index 00000000..e69de29b
    diff --git a/src/leap/bitmask/core/websocket.py b/src/leap/bitmask/core/websocket.py
    new file mode 100644
    index 00000000..5569c6c7
    --- /dev/null
    +++ b/src/leap/bitmask/core/websocket.py
    @@ -0,0 +1,98 @@
    +# -*- coding: utf-8 -*-
    +# websocket.py
    +# Copyright (C) 2015, 2016 LEAP
    +#
    +# This program is free software: you can redistribute it and/or modify
    +# it under the terms of the GNU General Public License as published by
    +# the Free Software Foundation, either version 3 of the License, or
    +# (at your option) any later version.
    +#
    +# This program 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 General Public License for more details.
    +#
    +# You should have received a copy of the GNU General Public License
    +# along with this program.  If not, see .
    +
    +"""
    +WebSockets Dispatcher Service.
    +"""
    +
    +import os
    +import pkg_resources
    +
    +from twisted.internet import reactor
    +from twisted.application import service
    +
    +from twisted.web.server import Site
    +from twisted.web.static import File
    +
    +from autobahn.twisted.resource import WebSocketResource
    +from autobahn.twisted.websocket import WebSocketServerFactory
    +from autobahn.twisted.websocket import WebSocketServerProtocol
    +
    +from leap.bitmask.core.dispatcher import CommandDispatcher
    +
    +
    +class WebSocketsDispatcherService(service.Service):
    +
    +    """
    +    A Dispatcher for BitmaskCore exposing a WebSockets Endpoint.
    +    """
    +
    +    def __init__(self, core, port=8080, debug=False):
    +        self._core = core
    +        self.port = port
    +        self.debug = debug
    +
    +    def startService(self):
    +
    +        factory = WebSocketServerFactory(u"ws://127.0.0.1:%d" % self.port,
    +                                         debug=self.debug)
    +        factory.protocol = DispatcherProtocol
    +        factory.protocol.dispatcher = CommandDispatcher(self._core)
    +
    +        # FIXME: Site.start/stopFactory should start/stop factories wrapped as
    +        # Resources
    +        factory.startFactory()
    +
    +        resource = WebSocketResource(factory)
    +
    +        # we server static files under "/" ..
    +        webdir = os.path.abspath(
    +            pkg_resources.resource_filename("leap.bitmask.core", "web"))
    +        root = File(webdir)
    +
    +        # and our WebSocket server under "/ws"
    +        root.putChild(u"bitmask", resource)
    +
    +        # both under one Twisted Web Site
    +        site = Site(root)
    +
    +        self.site = site
    +        self.factory = factory
    +
    +        self.listener = reactor.listenTCP(self.port, site)
    +
    +    def stopService(self):
    +        self.factory.stopFactory()
    +        self.site.stopFactory()
    +        self.listener.stopListening()
    +
    +
    +class DispatcherProtocol(WebSocketServerProtocol):
    +
    +    def onMessage(self, msg, binary):
    +        parts = msg.split()
    +        r = self.dispatcher.dispatch(parts)
    +        r.addCallback(self.defer_reply, binary)
    +
    +    def reply(self, response, binary):
    +        self.sendMessage(response, binary)
    +
    +    def defer_reply(self, response, binary):
    +        reactor.callLater(0, self.reply, response, binary)
    +
    +    def _get_service(self, name):
    +        return self.core.getServiceNamed(name)
    -- 
    cgit v1.2.3
    
    
    From 23d5143d7b66e30f84556be85eeced5f914598a9 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Tue, 8 Mar 2016 11:36:21 -0400
    Subject: [refactor] use __version__ directly
    
    ---
     src/leap/bitmask/core/service.py | 14 +++++++-------
     1 file changed, 7 insertions(+), 7 deletions(-)
    
    (limited to 'src/leap/bitmask')
    
    diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py
    index 4c18ab5d..87349ef6 100644
    --- a/src/leap/bitmask/core/service.py
    +++ b/src/leap/bitmask/core/service.py
    @@ -22,16 +22,14 @@ import resource
     from twisted.internet import reactor
     from twisted.python import log
     
    -from leap.bonafide.service import BonafideService
    -
    +from leap.bitmask import __version__
     from leap.bitmask.core import configurable
     from leap.bitmask.core import mail_services
     from leap.bitmask.core import _zmq
     from leap.bitmask.core import websocket
    -from leap.bitmask.core._version import get_versions
    -
    +from leap.bonafide.service import BonafideService
     from leap.common.events import server as event_server
    -from leap.vpn import EIPService
    +#from leap.vpn import EIPService
     
     
     class BitmaskBackend(configurable.ConfigurableService):
    @@ -96,7 +94,9 @@ class BitmaskBackend(configurable.ConfigurableService):
             self._maybe_start_service('mail', service, self.basedir)
     
         def init_eip(self):
    -        self._maybe_start_service('eip', EIPService)
    +        # FIXME -- land EIP into leap.vpn
    +        pass
    +        #self._maybe_start_service('eip', EIPService)
     
         def init_zmq(self):
             zs = _zmq.ZMQServerService(self)
    @@ -139,7 +139,7 @@ class BitmaskBackend(configurable.ConfigurableService):
             return " ".join(status_messages)
     
         def do_version(self):
    -        version = get_versions()['version']
    +        version = __version__
             return 'BitmaskCore: %s' % version
     
         def do_shutdown(self):
    -- 
    cgit v1.2.3
    
    
    From b3f5538984502d97c77a9666f39c8d5b8f4a5c31 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Tue, 8 Mar 2016 11:36:37 -0400
    Subject: [feature] add bitmaskd entrypoint
    
    ---
     src/leap/bitmask/core/launcher.py | 4 ++++
     1 file changed, 4 insertions(+)
    
    (limited to 'src/leap/bitmask')
    
    diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py
    index 7d658017..e363046e 100644
    --- a/src/leap/bitmask/core/launcher.py
    +++ b/src/leap/bitmask/core/launcher.py
    @@ -35,3 +35,7 @@ def run_bitmaskd():
         ]
         print '[+] launching bitmaskd...'
         run()
    +
    +
    +if __name__ == "__main__":
    +    run_bitmaskd()
    -- 
    cgit v1.2.3
    
    
    From 449bb9f2336d2f50a63d8558d1198b64b1ec4814 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 16:27:36 -0400
    Subject: [pkg] adapt launcher to work inside frozen binary
    
    ---
     src/leap/bitmask/core/launcher.py | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    (limited to 'src/leap/bitmask')
    
    diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py
    index e363046e..b2319077 100644
    --- a/src/leap/bitmask/core/launcher.py
    +++ b/src/leap/bitmask/core/launcher.py
    @@ -18,9 +18,10 @@
     Run bitmask daemon.
     """
     from twisted.scripts.twistd import run
    -from os.path import join, dirname
    +from os.path import join
     from sys import argv
     
    +from leap.bitmask.util import here
     from leap.bitmask import core
     
     
    @@ -28,7 +29,7 @@ def run_bitmaskd():
         # TODO --- configure where to put the logs... (get --logfile, --logdir
         # from the bitmask_cli
         argv[1:] = [
    -        '-y', join(dirname(core.__file__), "bitmaskd.tac"),
    +        '-y', join(here(core), "bitmaskd.tac"),
             '--pidfile', '/tmp/bitmaskd.pid',
             '--logfile', '/tmp/bitmaskd.log',
             '--umask=0022',
    -- 
    cgit v1.2.3
    
    
    From c8de65eab85a8fd0a416dbcc3282f5fc105a5716 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 16:34:43 -0400
    Subject: [feature] add logging
    
    ---
     src/leap/bitmask/core/mail_services.py | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    (limited to 'src/leap/bitmask')
    
    diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py
    index 9858d498..ba428258 100644
    --- a/src/leap/bitmask/core/mail_services.py
    +++ b/src/leap/bitmask/core/mail_services.py
    @@ -330,7 +330,7 @@ class KeymanagerService(service.Service, HookableService):
                     log.msg('Passing a new SRP Token to Keymanager: %s' % userid)
                     container.set_remote_auth_token(userid, token)
                 else:
    -                log.msg('storing the keymanager token...')
    +                log.msg('storing the keymanager token... %s ' % token)
                     self.tokens[userid] = token
     
         # commands
    -- 
    cgit v1.2.3
    
    
    From 9130d203bf15629c51dcc90bce83a8cd730900c4 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Wed, 9 Mar 2016 22:34:10 -0400
    Subject: [refactor] use latest implementation of service_hooks
    
    ---
     src/leap/bitmask/core/mail_services.py | 8 ++------
     1 file changed, 2 insertions(+), 6 deletions(-)
    
    (limited to 'src/leap/bitmask')
    
    diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py
    index ba428258..a0be1211 100644
    --- a/src/leap/bitmask/core/mail_services.py
    +++ b/src/leap/bitmask/core/mail_services.py
    @@ -135,9 +135,7 @@ def is_service_ready(service, provider):
         return has_service and has_config and is_enabled
     
     
    -class SoledadService(service.Service, HookableService):
    -
    -    subscribed_to_hooks = ('on_bonafide_auth', 'on_passphrase_entry')
    +class SoledadService(HookableService):
     
         def __init__(self, basedir):
             service.Service.__init__(self)
    @@ -286,9 +284,7 @@ class KeymanagerContainer(Container):
                 provider=provider)
     
     
    -class KeymanagerService(service.Service, HookableService):
    -
    -    subscribed_to_hooks = ('on_new_soledad_instance', 'on_bonafide_auth')
    +class KeymanagerService(HookableService):
     
         def __init__(self, basedir='~/.config/leap'):
             service.Service.__init__(self)
    -- 
    cgit v1.2.3
    
    
    From c6db8a1f9ba7b6fc9dd117c0a699f4dfd339a375 Mon Sep 17 00:00:00 2001
    From: Kali Kaneko 
    Date: Mon, 18 Apr 2016 18:27:29 -0400
    Subject: some fixes after review
    
    ---
     src/leap/bitmask/core/configurable.py  | 174 ++-------------------------------
     src/leap/bitmask/core/dispatcher.py    |   1 +
     src/leap/bitmask/core/mail_services.py |  64 +++++++-----
     3 files changed, 52 insertions(+), 187 deletions(-)
    
    (limited to 'src/leap/bitmask')
    
    diff --git a/src/leap/bitmask/core/configurable.py b/src/leap/bitmask/core/configurable.py
    index 3b97916d..8e33de95 100644
    --- a/src/leap/bitmask/core/configurable.py
    +++ b/src/leap/bitmask/core/configurable.py
    @@ -18,15 +18,15 @@
     Configurable Backend for Bitmask Service.
     """
     import ConfigParser
    -import locale
     import os
    -import re
    -import sys
     
     from twisted.application import service
    -from twisted.python import log
     
     from leap.common import files
    +from leap.common.config import get_path_prefix
    +
    +
    +DEFAULT_BASEDIR = os.path.join(get_path_prefix(), 'leap')
     
     
     class MissingConfigEntry(Exception):
    @@ -40,7 +40,7 @@ class ConfigurableService(service.MultiService):
         config_file = u"bitmaskd.cfg"
         service_names = ('mail', 'eip', 'zmq', 'web')
     
    -    def __init__(self, basedir='~/.config/leap'):
    +    def __init__(self, basedir=DEFAULT_BASEDIR):
             service.MultiService.__init__(self)
     
             path = os.path.abspath(os.path.expanduser(basedir))
    @@ -61,10 +61,9 @@ class ConfigurableService(service.MultiService):
     
             except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
                 if default is None:
    -                fn = os.path.join(self.basedir, self.config_file)
    +                fn = self._get_config_path()
                     raise MissingConfigEntry("%s is missing the [%s]%s entry"
    -                                         % (_quote_output(fn),
    -                                            section, option))
    +                                         % fn, section, option)
                 return default
     
         def set_config(self, section, option, value):
    @@ -82,12 +81,8 @@ class ConfigurableService(service.MultiService):
             if not os.path.isfile(bitmaskd_cfg):
                 self._create_default_config(bitmaskd_cfg)
     
    -        try:
    -            with open(bitmaskd_cfg, "rb") as f:
    -                self.config.readfp(f)
    -        except EnvironmentError:
    -            if os.path.exists(bitmaskd_cfg):
    -                raise
    +        with open(bitmaskd_cfg, "rb") as f:
    +            self.config.readfp(f)
     
         def save_config(self):
             bitmaskd_cfg = self._get_config_path()
    @@ -109,154 +104,3 @@ eip = True
     zmq = True
     web = False
     """
    -
    -
    -def canonical_encoding(encoding):
    -    if encoding is None:
    -        log.msg("Warning: falling back to UTF-8 encoding.", level=log.WEIRD)
    -        encoding = 'utf-8'
    -    encoding = encoding.lower()
    -    if encoding == "cp65001":
    -        encoding = 'utf-8'
    -    elif (encoding == "us-ascii" or encoding == "646" or encoding ==
    -          "ansi_x3.4-1968"):
    -        encoding = 'ascii'
    -
    -    return encoding
    -
    -
    -def check_encoding(encoding):
    -    # sometimes Python returns an encoding name that it doesn't support for
    -    # conversion fail early if this happens
    -    try:
    -        u"test".encode(encoding)
    -    except (LookupError, AttributeError):
    -        raise AssertionError(
    -            "The character encoding '%s' is not supported for conversion." % (
    -                encoding,))
    -
    -filesystem_encoding = None
    -io_encoding = None
    -is_unicode_platform = False
    -
    -
    -def _reload():
    -    global filesystem_encoding, io_encoding, is_unicode_platform
    -
    -    filesystem_encoding = canonical_encoding(sys.getfilesystemencoding())
    -    check_encoding(filesystem_encoding)
    -
    -    if sys.platform == 'win32':
    -        # On Windows we install UTF-8 stream wrappers for sys.stdout and
    -        # sys.stderr, and reencode the arguments as UTF-8 (see
    -        # scripts/runner.py).
    -        io_encoding = 'utf-8'
    -    else:
    -        ioenc = None
    -        if hasattr(sys.stdout, 'encoding'):
    -            ioenc = sys.stdout.encoding
    -        if ioenc is None:
    -            try:
    -                ioenc = locale.getpreferredencoding()
    -            except Exception:
    -                pass  # work around 
    -        io_encoding = canonical_encoding(ioenc)
    -
    -    check_encoding(io_encoding)
    -
    -    is_unicode_platform = sys.platform in ["win32", "darwin"]
    -
    -_reload()
    -
    -
    -def _quote_output(s, quotemarks=True, quote_newlines=None, encoding=None):
    -    """
    -    Encode either a Unicode string or a UTF-8-encoded bytestring for
    -    representation on stdout or stderr, tolerating errors. If 'quotemarks' is
    -    True, the string is always quoted; otherwise, it is quoted only if
    -    necessary to avoid ambiguity or control bytes in the output. (Newlines are
    -    counted as control bytes iff quote_newlines is True.)
    -
    -    Quoting may use either single or double quotes. Within single quotes, all
    -    characters stand for themselves, and ' will not appear. Within double
    -    quotes, Python-compatible backslash escaping is used.
    -
    -    If not explicitly given, quote_newlines is True when quotemarks is True.
    -    """
    -    assert isinstance(s, (str, unicode))
    -    if quote_newlines is None:
    -        quote_newlines = quotemarks
    -
    -    if isinstance(s, str):
    -        try:
    -            s = s.decode('utf-8')
    -        except UnicodeDecodeError:
    -            return 'b"%s"' % (
    -                ESCAPABLE_8BIT.sub(
    -                    lambda m: _str_escape(m, quote_newlines), s),)
    -
    -    must_double_quote = (quote_newlines and MUST_DOUBLE_QUOTE_NL or
    -                         MUST_DOUBLE_QUOTE)
    -    if must_double_quote.search(s) is None:
    -        try:
    -            out = s.encode(encoding or io_encoding)
    -            if quotemarks or out.startswith('"'):
    -                return "'%s'" % (out,)
    -            else:
    -                return out
    -        except (UnicodeDecodeError, UnicodeEncodeError):
    -            pass
    -
    -    escaped = ESCAPABLE_UNICODE.sub(
    -        lambda m: _unicode_escape(m, quote_newlines), s)
    -    return '"%s"' % (
    -        escaped.encode(encoding or io_encoding, 'backslashreplace'),)
    -
    -
    -def _unicode_escape(m, quote_newlines):
    -    u = m.group(0)
    -    if u == u'"' or u == u'$' or u == u'`' or u == u'\\':
    -        return u'\\' + u
    -    elif u == u'\n' and not quote_newlines:
    -        return u
    -    if len(u) == 2:
    -        codepoint = (
    -            ord(u[0]) - 0xD800) * 0x400 + ord(u[1]) - 0xDC00 + 0x10000
    -    else:
    -        codepoint = ord(u)
    -    if codepoint > 0xFFFF:
    -        return u'\\U%08x' % (codepoint,)
    -    elif codepoint > 0xFF:
    -        return u'\\u%04x' % (codepoint,)
    -    else:
    -        return u'\\x%02x' % (codepoint,)
    -
    -
    -def _str_escape(m, quote_newlines):
    -    c = m.group(0)
    -    if c == '"' or c == '$' or c == '`' or c == '\\':
    -        return '\\' + c
    -    elif c == '\n' and not quote_newlines:
    -        return c
    -    else:
    -        return '\\x%02x' % (ord(c),)
    -
    -MUST_DOUBLE_QUOTE_NL = re.compile(
    -    ur'[^\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]',
    -    re.DOTALL)
    -MUST_DOUBLE_QUOTE = re.compile(
    -    ur'[^\n\x20-\x26\x28-\x7E\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFC]',
    -    re.DOTALL)
    -
    -ESCAPABLE_8BIT = re.compile(
    -    r'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E]',
    -    re.DOTALL)
    -
    -# if we must double-quote, then we have to escape ", $ and `, but need not
    -# escape '
    -
    -ESCAPABLE_UNICODE = re.compile(
    -    ur'([\uD800-\uDBFF][\uDC00-\uDFFF])|'  # valid surrogate pairs
    -    ur'[^ !#\x25-\x5B\x5D-\x5F\x61-\x7E\u00A0-\uD7FF'
    -    ur'\uE000-\uFDCF\uFDF0-\uFFFC]',
    -    re.DOTALL)
    diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py
    index 4d7e1813..648cbe9b 100644
    --- a/src/leap/bitmask/core/dispatcher.py
    +++ b/src/leap/bitmask/core/dispatcher.py
    @@ -84,6 +84,7 @@ class CommandDispatcher(object):
             return d
     
         def do_EIP(self, *parts):
    +
             subcmd = parts[1]
             eip_label = 'eip'
     
    diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py
    index a0be1211..6472bdc2 100644
    --- a/src/leap/bitmask/core/mail_services.py
    +++ b/src/leap/bitmask/core/mail_services.py
    @@ -1,3 +1,19 @@
    +# -*- coding: utf-8 -*-
    +# mail_services.py
    +# Copyright (C) 2016 LEAP Encryption Acess Project
    +#
    +# This program is free software: you can redistribute it and/or modify
    +# it under the terms of the GNU General Public License as published by
    +# the Free Software Foundation, either version 3 of the License, or
    +# (at your option) any later version.
    +#
    +# This program 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 General Public License for more details.
    +#
    +# You should have received a copy of the GNU General Public License
    +# along with this program.  If not, see .
     """
     Mail services.
     
    @@ -6,7 +22,6 @@ This should be moved to the different packages when it stabilizes.
     """
     import json
     import os
    -from glob import glob
     from collections import defaultdict
     from collections import namedtuple
     
    @@ -26,31 +41,30 @@ from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD
     from leap.mail import smtp
     
     from leap.bitmask.core.uuid_map import UserMap
    +from leap.bitmask.core.configurable import DEFAULT_BASEDIR
     
     
     class Container(object):
     
    -    def __init__(self):
    +    def __init__(self, service=None):
             self._instances = defaultdict(None)
    +        if service is not None:
    +            self.service = service
     
         def get_instance(self, key):
             return self._instances.get(key, None)
     
    +    def add_instance(self, key, data):
    +        self._instances[key] = data
    +
     
     class ImproperlyConfigured(Exception):
         pass
     
     
    -def get_all_soledad_uuids():
    -    return [os.path.split(p)[-1].split('.db')[0] for p in
    -            glob(os.path.expanduser('~/.config/leap/soledad/*.db'))]
    -    # FIXME do not hardcode basedir
    -
    -
     class SoledadContainer(Container):
     
    -    def __init__(self, basedir='~/.config/leap'):
    -        # FIXME do not hardcode basedir
    +    def __init__(self, basedir=DEFAULT_BASEDIR):
             self._basedir = os.path.expanduser(basedir)
             self._usermap = UserMap()
             super(SoledadContainer, self).__init__()
    @@ -75,19 +89,17 @@ class SoledadContainer(Container):
                 uuid, passphrase, soledad_path, soledad_url,
                 cert_path, token)
     
    -        self._instances[userid] = soledad
    +        self.add_instances(userid, soledad)
     
             data = {'user': userid, 'uuid': uuid, 'token': token,
                     'soledad': soledad}
             self.service.trigger_hook('on_new_soledad_instance', **data)
     
    -    def _create_soledad_instance(self, uuid, passphrase, basedir, server_url,
    -                                 cert_file, token):
    +    def _create_soledad_instance(self, uuid, passphrase, soledad_path,
    +                                 server_url, cert_file, token):
             # setup soledad info
    -        secrets_path = os.path.join(
    -            basedir, '%s.secret' % uuid)
    -        local_db_path = os.path.join(
    -            basedir, '%s.db' % uuid)
    +        secrets_path = os.path.join(soledad_path, '%s.secret' % uuid)
    +        local_db_path = os.path.join(soledad_path, '%s.db' % uuid)
     
             if token is None:
                 syncable = False
    @@ -143,8 +155,7 @@ class SoledadService(HookableService):
     
         def startService(self):
             log.msg('Starting Soledad Service')
    -        self._container = SoledadContainer()
    -        self._container.service = self
    +        self._container = SoledadContainer(service=self)
             super(SoledadService, self).startService()
     
         # hooks
    @@ -209,7 +220,7 @@ class KeymanagerContainer(Container):
     
         def _on_keymanager_ready_cb(self, keymanager, userid, soledad):
             # TODO use onready-deferreds instead
    -        self._instances[userid] = keymanager
    +        self.add_instance(userid, keymanager)
     
             log.msg("Adding Keymanager instance for: %s" % userid)
             data = {'userid': userid, 'soledad': soledad, 'keymanager': keymanager}
    @@ -264,6 +275,11 @@ class KeymanagerContainer(Container):
                 token = self.service.tokens.get(userid)
     
             km_args = (userid, nickserver_uri, soledad)
    +
    +        # TODO use the method in
    +        # services.soledadbootstrapper._get_gpg_bin_path.
    +        # That should probably live in keymanager package.
    +
             km_kwargs = {
                 "token": token, "uid": uuid,
                 "api_uri": api_uri, "api_version": "1",
    @@ -373,10 +389,12 @@ class StandardMailService(service.MultiService, HookableService):
     
         def initializeChildrenServices(self):
             self.addService(IMAPService(self._soledad_sessions))
    -        self.addService(IncomingMailService(self))
             self.addService(SMTPService(
                 self._soledad_sessions, self._keymanager_sessions,
                 self._sendmail_opts))
    +        # TODO adapt the service to receive soledad/keymanager sessions object.
    +        # See also the TODO before IncomingMailService.startInstance
    +        self.addService(IncomingMailService(self))
     
         def startService(self):
             log.msg('Starting Mail Service...')
    @@ -438,6 +456,7 @@ class StandardMailService(service.MultiService, HookableService):
             if not active_user:
                 return defer.succeed('NO ACTIVE USER')
             token = self._imap_tokens.get(active_user)
    +        # TODO return just the tuple, no format.
             return defer.succeed("IMAP TOKEN (%s): %s" % (active_user, token))
     
         def get_smtp_token(self):
    @@ -445,6 +464,7 @@ class StandardMailService(service.MultiService, HookableService):
             if not active_user:
                 return defer.succeed('NO ACTIVE USER')
             token = self._smtp_tokens.get(active_user)
    +        # TODO return just the tuple, no format.
             return defer.succeed("SMTP TOKEN (%s): %s" % (active_user, token))
     
         def do_get_smtp_cert_path(self, userid):
    @@ -560,7 +580,7 @@ class IncomingMailService(service.Service):
                 if start_sync:
                     incoming_instance.startService()
     
    -        acc = Account(soledad)
    +        acc = Account(soledad, userid)
             d = acc.callWhenReady(
                 lambda _: acc.get_collection_by_mailbox(INBOX_NAME))
             d.addCallback(setUpIncomingMail)
    -- 
    cgit v1.2.3
    
    
    From 229f803235ae9b9a71313d11071c7a0fbea0a681 Mon Sep 17 00:00:00 2001
    From: elijah 
    Date: Mon, 18 Apr 2016 23:44:12 -0700
    Subject: [feature] add email panel to preferences
    
    ---
     src/leap/bitmask/backend/components.py            |  20 +-
     src/leap/bitmask/gui/account.py                   |  10 +-
     src/leap/bitmask/gui/app.py                       |  34 ++
     src/leap/bitmask/gui/mainwindow.py                |  32 +-
     src/leap/bitmask/gui/passwordwindow.py            |   2 +-
     src/leap/bitmask/gui/preferences_account_page.py  |  31 +-
     src/leap/bitmask/gui/preferences_email_page.py    | 184 ++++++-
     src/leap/bitmask/gui/preferences_page.py          |  50 ++
     src/leap/bitmask/gui/preferences_vpn_page.py      |  31 +-
     src/leap/bitmask/gui/preferenceswindow.py         | 131 +++--
     src/leap/bitmask/gui/ui/preferences.ui            |  22 +-
     src/leap/bitmask/gui/ui/preferences_email_page.ui | 563 +++++++++++++++++++++-
     12 files changed, 1006 insertions(+), 104 deletions(-)
     create mode 100644 src/leap/bitmask/gui/preferences_page.py
    
    (limited to 'src/leap/bitmask')
    
    diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py
    index acb562c7..3192e1c4 100644
    --- a/src/leap/bitmask/backend/components.py
    +++ b/src/leap/bitmask/backend/components.py
    @@ -576,8 +576,10 @@ class EIP(object):
                         self._signaler.eip_uninitialized_provider)
                 return
     
    -        eip_config = eipconfig.EIPConfig()
             provider_config = ProviderConfig.get_provider_config(domain)
    +        if EIP_SERVICE not in provider_config.get_services():
    +            return
    +        eip_config = eipconfig.EIPConfig()
     
             api_version = provider_config.get_api_version()
             eip_config.set_api_version(api_version)
    @@ -1003,13 +1005,11 @@ class Keymanager(object):
     
         def get_key_details(self, username):
             """
    -        List all the keys stored in the local DB.
    +        Get information on our primary key pair
             """
             def signal_details(public_key):
    -            # XXX: We should avoid the key-id
    -            details = (public_key.fingerprint[-16:], public_key.fingerprint)
                 self._signaler.signal(self._signaler.keymanager_key_details,
    -                                  details)
    +                                  public_key.get_dict())
     
             d = self._keymanager_proxy.get_key(username,
                                                openpgp.OpenPGPKey)
    @@ -1215,15 +1215,13 @@ class Authenticate(object):
     
         def get_logged_in_status(self):
             """
    -        Signal if the user is currently logged in or not.
    +        Signal if the user is currently logged in or not. If logged in,
    +        authenticated username is passed as argument to the signal.
             """
             if self._signaler is None:
                 return
     
    -        signal = None
             if self._is_logged_in():
    -            signal = self._signaler.srp_status_logged_in
    +            self._signaler.signal(self._signaler.srp_status_logged_in)
             else:
    -            signal = self._signaler.srp_status_not_logged_in
    -
    -        self._signaler.signal(signal)
    +            self._signaler.signal(self._signaler.srp_status_not_logged_in)
    diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py
    index 81f96389..5e43c8fe 100644
    --- a/src/leap/bitmask/gui/account.py
    +++ b/src/leap/bitmask/gui/account.py
    @@ -20,7 +20,7 @@ A frontend GUI object to hold the current username and domain.
     from leap.bitmask.util import make_address
     from leap.bitmask.config.leapsettings import LeapSettings
     from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
    -
    +from leap.bitmask._components import HAS_EIP, HAS_MAIL
     
     class Account():
     
    @@ -42,8 +42,8 @@ class Account():
             """
             return self._settings.get_enabled_services(self.domain)
     
    -    def is_email_enabled(self):
    -        return MX_SERVICE in self.services()
    +    def has_email(self):
    +        return HAS_MAIL and MX_SERVICE in self.services()
     
    -    def is_eip_enabled(self):
    -        return EIP_SERVICE in self.services()
    +    def has_eip(self):
    +        return HAS_EIP and EIP_SERVICE in self.services()
    diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py
    index 97fd0549..e3a4d7fe 100644
    --- a/src/leap/bitmask/gui/app.py
    +++ b/src/leap/bitmask/gui/app.py
    @@ -20,6 +20,7 @@ and the signaler get signals from the backend.
     """
     from PySide import QtCore, QtGui
     
    +from leap.bitmask.gui.account import Account
     from leap.bitmask.config.leapsettings import LeapSettings
     from leap.bitmask.backend.backend_proxy import BackendProxy
     from leap.bitmask.backend.leapsignaler import LeapSignaler
    @@ -44,12 +45,37 @@ class App(QtGui.QWidget):
             self.signaler.start()
     
             self.soledad_started = False
    +        self.service_tokens = {}
    +        self.login_state = None
    +        self.providers_widget = None
     
             # periodically check if the backend is alive
             self._backend_checker = QtCore.QTimer(self)
             self._backend_checker.timeout.connect(self._check_backend_status)
             self._backend_checker.start(2000)
     
    +        # store the service tokens for later use, once they are known.
    +        self.signaler.soledad_got_service_token.connect(
    +            self._set_service_tokens)
    +
    +    def current_account(self):
    +        """
    +        Alas, the only definitive account information is buried in the memory of
    +        QT widgets.
    +
    +        :returns: an object representing the current user account.
    +        :rtype: Account
    +        """
    +        if self.login_state is None or self.providers_widget is None:
    +            return None
    +
    +        if self.login_state.full_logged_username is not None:
    +            username, domain = self.login_state.full_logged_username.split('@')
    +            return Account(username, domain)
    +        else:
    +            domain = self.providers_widget.get_selected_provider()
    +            return Account(None, domain)
    +
         def _check_backend_status(self):
             """
             TRIGGERS:
    @@ -64,3 +90,11 @@ class App(QtGui.QWidget):
                     self.tr("There is a problem contacting the backend, please "
                             "restart Bitmask."))
                 self._backend_checker.stop()
    +
    +    def _set_service_tokens(self, data):
    +        """
    +        Triggered by signal soledad_got_service_token.
    +        Saves the service tokens.
    +        """
    +        service, token = data
    +        self.service_tokens[service] = token
    diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
    index cde44f7b..ca14e631 100644
    --- a/src/leap/bitmask/gui/mainwindow.py
    +++ b/src/leap/bitmask/gui/mainwindow.py
    @@ -46,7 +46,6 @@ from leap.bitmask.gui.signaltracker import SignalTracker
     from leap.bitmask.gui.systray import SysTray
     from leap.bitmask.gui.wizard import Wizard
     from leap.bitmask.gui.providers import Providers
    -from leap.bitmask.gui.account import Account
     from leap.bitmask.gui.app import App
     
     from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX
    @@ -154,6 +153,14 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             # Provider List
             self._providers = Providers(self.ui.cmbProviders)
     
    +        ##
    +        ## tmphack: important state information about the application is stored
    +        ## in widgets. Rather than rewrite the UI, for now we simulate this
    +        ## info being stored in an application object:
    +        ##
    +        self.app.login_state      = self._login_widget._state
    +        self.app.providers_widget = self._providers
    +
             # Qt Signal Connections #####################################
             # TODO separate logic from ui signals.
     
    @@ -416,10 +423,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             sig.soledad_invalid_auth_token.connect(
                 self._mail_status.set_soledad_invalid_auth_token)
     
    -        self._service_tokens = {}
    -        sig.soledad_got_service_token.connect(
    -            self._set_service_tokens)
    -
             # TODO: connect this with something
             # sig.soledad_cancelled_bootstrap.connect()
     
    @@ -569,15 +572,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
     
             Display the preferences window.
             """
    -        logged_user = self._login_widget.get_logged_user()
    -        if logged_user is not None:
    -            user, domain = logged_user.split('@')
    -        else:
    -            user = None
    -            domain = self._providers.get_selected_provider()
    -
    -        account = Account(user, domain)
    -        pref_win = PreferencesWindow(self, account, self.app)
    +        pref_win = PreferencesWindow(self, self.app)
             pref_win.show()
     
         def _show_pixelated_browser(self):
    @@ -1054,13 +1049,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10], greet=greet)
             QtGui.QMessageBox.about(self, title, msg)
     
    -    def _set_service_tokens(self, data):
    -        """
    -        Set the received service token.
    -        """
    -        service, token = data
    -        self._service_tokens[service] = token
    -
         def _help(self):
             """
             TRIGGERS:
    @@ -1095,7 +1083,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker):
             # FIXME on i3, this doens't allow to mouse-select.
             # Switch to a dialog in which we can set the QLabel
             mail_auth_token = (
    -            self._service_tokens.get('mail_auth', None) or
    +            self.app.service_tokens.get('mail_auth', None) or
                 "??? (log in to unlock)")
             mail_password = self.tr("IMAP/SMTP Password:") + " %s" % (
                 mail_auth_token,)
    diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py
    index dedfcb10..fe70b250 100644
    --- a/src/leap/bitmask/gui/passwordwindow.py
    +++ b/src/leap/bitmask/gui/passwordwindow.py
    @@ -83,7 +83,7 @@ class PasswordWindow(QtGui.QDialog, Flashable):
             Returns true if the current account needs to change the soledad
             password as well as the SRP password.
             """
    -        return self.account.is_email_enabled()
    +        return self.account.has_email()
     
         #
         # MANAGE WIDGETS
    diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py
    index da9da14d..c175c42b 100644
    --- a/src/leap/bitmask/gui/preferences_account_page.py
    +++ b/src/leap/bitmask/gui/preferences_account_page.py
    @@ -22,6 +22,7 @@ from PySide import QtCore, QtGui
     
     from leap.bitmask.logs.utils import get_logger
     from leap.bitmask.gui import ui_preferences_account_page as ui_pref
    +from leap.bitmask.gui.preferences_page import PreferencesPage
     from leap.bitmask.gui.passwordwindow import PasswordWindow
     from leap.bitmask.services import get_service_display_name
     from leap.bitmask._components import HAS_EIP
    @@ -29,7 +30,7 @@ from leap.bitmask._components import HAS_EIP
     logger = get_logger()
     
     
    -class PreferencesAccountPage(QtGui.QWidget):
    +class PreferencesAccountPage(PreferencesPage):
     
         def __init__(self, parent, account, app):
             """
    @@ -42,20 +43,15 @@ class PreferencesAccountPage(QtGui.QWidget):
             :param app: the current App object
             :type app: App
             """
    -        QtGui.QWidget.__init__(self, parent)
    +        PreferencesPage.__init__(self, parent, account, app)
             self.ui = ui_pref.Ui_PreferencesAccountPage()
             self.ui.setupUi(self)
     
    -        self.account = account
    -        self.app = app
    -
             self._selected_services = set()
             self.ui.change_password_label.setVisible(False)
             self.ui.provider_services_label.setVisible(False)
     
    -        self.ui.change_password_button.clicked.connect(
    -            self._show_change_password)
    -        app.signaler.prov_get_supported_services.connect(self._load_services)
    +        self.setup_connections()
             app.backend.provider_get_supported_services(domain=account.domain)
     
             if account.username is None:
    @@ -64,6 +60,25 @@ class PreferencesAccountPage(QtGui.QWidget):
                 self.ui.change_password_label.setVisible(True)
                 self.ui.change_password_button.setEnabled(False)
     
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        """
    +        self.ui.change_password_button.clicked.connect(
    +            self._show_change_password)
    +        self.app.signaler.prov_get_supported_services.connect(
    +            self._load_services)
    +
    +
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        """
    +        self.ui.change_password_button.clicked.disconnect(
    +            self._show_change_password)
    +        self.app.signaler.prov_get_supported_services.disconnect(
    +            self._load_services)
    +
         def _service_selection_changed(self, service, state):
             """
             TRIGGERS:
    diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py
    index 3087f343..50f244fb 100644
    --- a/src/leap/bitmask/gui/preferences_email_page.py
    +++ b/src/leap/bitmask/gui/preferences_email_page.py
    @@ -16,20 +16,192 @@
     """
     Widget for "email" preferences
     """
    -from PySide import QtGui
    +from PySide import QtCore, QtGui
    +
    +from datetime import datetime
     
     from leap.bitmask.logs.utils import get_logger
     from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage
    +from leap.bitmask.gui.preferences_page import PreferencesPage
    +from leap.mail.imap.service.imap import IMAP_PORT
     
     logger = get_logger()
     
    -
    -class PreferencesEmailPage(QtGui.QWidget):
    +class PreferencesEmailPage(PreferencesPage):
     
         def __init__(self, parent, account, app):
    -        QtGui.QWidget.__init__(self, parent)
    +        """
    +        :param parent: parent object of the PreferencesWindow.
    +        :parent type: QWidget
    +
    +        :param account: user account (user + provider or just provider)
    +        :type account: Account
    +
    +        :param app: the current App object
    +        :type app: App
    +        """
    +        PreferencesPage.__init__(self, parent, account, app)
             self.ui = Ui_PreferencesEmailPage()
             self.ui.setupUi(self)
     
    -        self.account = account
    -        self.app = app
    +        # the only way to set the tab titles is to re-add them:
    +        self.ui.email_tabs.addTab(self.ui.config_tab,
    +          self.tr("Mail Client"))
    +        self.ui.email_tabs.addTab(self.ui.my_key_tab,
    +          self.tr("My Key"))
    +        self.ui.email_tabs.addTab(self.ui.other_keys_tab,
    +          self.tr("Other Keys"))
    +
    +        # set mail client configuration help text
    +        lang = QtCore.QLocale.system().name().replace('_', '-')
    +        thunderbird_extension_url = \
    +            "https://addons.mozilla.org/{0}/" \
    +            "thunderbird/addon/bitmask/".format(lang)
    +        self.ui.thunderbird_label.setText(self.tr(
    +            "For Thunderbird, you can use the Bitmask extension. "
    +            "Search for \"Bitmask\" in the add-on manager or "
    +            "download it from addons.mozilla.org.".format(
    +            thunderbird_extension_url)))
    +        self.ui.mail_client_label.setText(self.tr(
    +            "Alternatively, you can manually configure your mail client to "
    +            "use Bitmask Email with these options:"))
    +
    +        self.ui.keys_table.horizontalHeader().setResizeMode(
    +            0, QtGui.QHeaderView.Stretch)
    +
    +        self.setup_connections()
    +
    +
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        """
    +        self.app.signaler.keymanager_key_details.connect(self._key_details)
    +        self.app.signaler.keymanager_export_ok.connect(
    +            self._keymanager_export_ok)
    +        self.app.signaler.keymanager_export_error.connect(
    +            self._keymanager_export_error)
    +        self.ui.import_button.clicked.connect(self._import_keys)
    +        self.ui.export_button.clicked.connect(self._export_keys)
    +
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        """
    +        self.app.signaler.keymanager_key_details.disconnect(self._key_details)
    +        self.app.signaler.keymanager_export_ok.disconnect(
    +            self._keymanager_export_ok)
    +        self.app.signaler.keymanager_export_error.disconnect(
    +            self._keymanager_export_error)
    +
    +    def showEvent(self, event):
    +        """
    +        called whenever this widget is shown
    +        """
    +        self.ui.keys_table.clearContents()
    +
    +        if self.account.username is None:
    +            self.ui.email_tabs.setVisible(False)
    +            self.ui.message_label.setVisible(True)
    +            self.ui.message_label.setText(
    +                self.tr('You must be logged in to edit email settings.'))
    +        else:
    +            self.ui.import_button.setVisible(False) # hide this until working
    +            self.ui.message_label.setVisible(False)
    +            self.ui.email_tabs.setVisible(True)
    +            smtp_port = 2013
    +            self.ui.imap_port_edit.setText(str(IMAP_PORT))
    +            self.ui.imap_host_edit.setText("127.0.0.1")
    +            self.ui.smtp_port_edit.setText(str(smtp_port))
    +            self.ui.smtp_host_edit.setText("127.0.0.1")
    +            self.ui.username_edit.setText(self.account.address)
    +            self.ui.password_edit.setText(
    +                self.app.service_tokens.get('mail_auth', ''))
    +
    +            self.app.backend.keymanager_list_keys()
    +            self.app.backend.keymanager_get_key_details(
    +                username=self.account.address)
    +
    +    def _key_details(self, details):
    +        """
    +        Trigger by signal: keymanager_key_details
    +        Set the current user's key details into the gui.
    +        """
    +        self.ui.fingerprint_edit.setPlainText(
    +          self._format_fingerprint(details["fingerprint"]))
    +        self.ui.expiration_edit.setText(details["expiry_date"])
    +        self.ui.uid_edit.setText(" ".join(details["uids"]))
    +        self.ui.public_key_edit.setPlainText(details["key_data"])
    +
    +    def _format_fingerprint(self, fingerprint):
    +        """
    +        formats an openpgp fingerprint in a manner similar to what gpg
    +        produces, wrapped to two lines.
    +        """
    +        fp = fingerprint.upper()
    +        fp_list = [fp[i:i+4] for i in range(0, len(fp), 4)]
    +        fp_wrapped =  " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10])
    +        return fp_wrapped
    +
    +    def _export_keys(self):
    +        """
    +        Exports the user's key pair.
    +        """
    +        file_name, filtr = QtGui.QFileDialog.getSaveFileName(
    +            self, self.tr("Save private key file"),
    +            filter="*.pem",
    +            options=QtGui.QFileDialog.DontUseNativeDialog)
    +
    +        if file_name:
    +            if not file_name.endswith('.pem'):
    +                file_name += '.pem'
    +            self.app.backend.keymanager_export_keys(
    +              username=self.account.address,
    +              filename=file_name)
    +        else:
    +            logger.debug('Export canceled by the user.')
    +
    +    def _keymanager_export_ok(self):
    +        """
    +        TRIGGERS:
    +            Signaler.keymanager_export_ok
    +
    +        Notify the user that the key export went OK.
    +        """
    +        QtGui.QMessageBox.information(
    +            self, self.tr("Export Successful"),
    +            self.tr("The key pair was exported successfully.\n"
    +                    "Please, store your private key in a safe place."))
    +
    +    def _keymanager_export_error(self):
    +        """
    +        TRIGGERS:
    +            Signaler.keymanager_export_error
    +
    +        Notify the user that the key export didn't go well.
    +        """
    +        QtGui.QMessageBox.critical(
    +            self, self.tr("Input/Output error"),
    +            self.tr("There was an error accessing the file.\n"
    +                    "Export canceled."))
    +
    +    def _import_keys(self):
    +      """
    +      not yet supported
    +      """
    +
    +    def _keymanager_keys_list(self, keys):
    +        """
    +        TRIGGERS:
    +            Signaler.keymanager_keys_list
    +
    +        Load the keys given as parameter in the table.
    +
    +        :param keys: the list of keys to load.
    +        :type keys: list
    +        """
    +        for key in keys:
    +            row = self.ui.keys_table.rowCount()
    +            self.ui.keys_table.insertRow(row)
    +            self.ui.keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address))
    +            self.ui.keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.fingerprint))
    diff --git a/src/leap/bitmask/gui/preferences_page.py b/src/leap/bitmask/gui/preferences_page.py
    new file mode 100644
    index 00000000..c75b4991
    --- /dev/null
    +++ b/src/leap/bitmask/gui/preferences_page.py
    @@ -0,0 +1,50 @@
    +# -*- coding: utf-8 -*-
    +# Copyright (C) 2014 LEAP
    +#
    +# This program is free software: you can redistribute it and/or modify
    +# it under the terms of the GNU General Public License as published by
    +# the Free Software Foundation, either version 3 of the License, or
    +# (at your option) any later version.
    +#
    +# This program 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 General Public License for more details.
    +#
    +# You should have received a copy of the GNU General Public License
    +# along with this program.  If not, see .
    +"""
    +base class for preference pages
    +"""
    +
    +from PySide import QtCore, QtGui
    +
    +class PreferencesPage(QtGui.QWidget):
    +
    +    def __init__(self, parent, account=None, app=None):
    +        """
    +        :param parent: parent object of the EIPPreferencesWindow.
    +        :type parent: QWidget
    +
    +        :param account: the currently active account
    +        :type account: Account
    +
    +        :param app: shared App instance
    +        :type app: App
    +        """
    +        QtGui.QWidget.__init__(self, parent)
    +        self.app = app
    +        self.account = account
    +
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        must be overridden by subclass
    +        """
    +
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        must be overridden by subclass
    +        """
    +
    diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py
    index 5b5c9604..fc15340f 100644
    --- a/src/leap/bitmask/gui/preferences_vpn_page.py
    +++ b/src/leap/bitmask/gui/preferences_vpn_page.py
    @@ -22,9 +22,9 @@ from leap.bitmask.gui.ui_preferences_vpn_page import Ui_PreferencesVpnPage
     
     from leap.bitmask.config.leapsettings import LeapSettings
     from leap.bitmask.gui.flashable import Flashable
    +from leap.bitmask.gui.preferences_page import PreferencesPage
     
    -
    -class PreferencesVpnPage(QtGui.QWidget, Flashable):
    +class PreferencesVpnPage(PreferencesPage, Flashable):
     
         """
         Page in the preferences window that shows VPN settings
    @@ -41,19 +41,24 @@ class PreferencesVpnPage(QtGui.QWidget, Flashable):
             :param app: shared App instance
             :type app: App
             """
    -        QtGui.QWidget.__init__(self, parent)
    +        PreferencesPage.__init__(self, parent, account, app)
             self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
     
    -        self.account = account
    -        self.app = app
    -
             # Load UI
             self.ui = Ui_PreferencesVpnPage()
             self.ui.setupUi(self)
             self.ui.flash_label.setVisible(False)
             self.hide_flash()
     
    -        # Connections
    +        self.setup_connections()
    +
    +        # Trigger update
    +        self.app.backend.eip_get_gateways_list(domain=self.account.domain)
    +
    +    def setup_connections(self):
    +        """
    +        connect signals
    +        """
             self.ui.gateways_list.clicked.connect(self._save_selected_gateway)
             sig = self.app.signaler
             sig.eip_get_gateways_list.connect(self._update_gateways_list)
    @@ -61,8 +66,16 @@ class PreferencesVpnPage(QtGui.QWidget, Flashable):
             sig.eip_uninitialized_provider.connect(
                 self._gateways_list_uninitialized)
     
    -        # Trigger update
    -        self.app.backend.eip_get_gateways_list(domain=self.account.domain)
    +    def teardown_connections(self):
    +        """
    +        disconnect signals
    +        """
    +        self.ui.gateways_list.clicked.disconnect(self._save_selected_gateway)
    +        sig = self.app.signaler
    +        sig.eip_get_gateways_list.disconnect(self._update_gateways_list)
    +        sig.eip_get_gateways_list_error.disconnect(self._gateways_list_error)
    +        sig.eip_uninitialized_provider.disconnect(
    +            self._gateways_list_uninitialized)
     
         def _save_selected_gateway(self, index):
             """
    diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
    index 44c4641c..1facba69 100644
    --- a/src/leap/bitmask/gui/preferenceswindow.py
    +++ b/src/leap/bitmask/gui/preferenceswindow.py
    @@ -20,18 +20,15 @@ Preferences window
     """
     from PySide import QtCore, QtGui
     
    -from leap.bitmask.services import EIP_SERVICE
    -from leap.bitmask._components import HAS_EIP
    -
     from leap.bitmask.logs.utils import get_logger
     from leap.bitmask.gui.ui_preferences import Ui_Preferences
    +from leap.bitmask.gui.preferences_page import PreferencesPage
     from leap.bitmask.gui.preferences_account_page import PreferencesAccountPage
     from leap.bitmask.gui.preferences_vpn_page import PreferencesVpnPage
     from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage
     
     logger = get_logger()
     
    -
     class PreferencesWindow(QtGui.QDialog):
     
         """
    @@ -40,39 +37,46 @@ class PreferencesWindow(QtGui.QDialog):
     
         _current_window = None  # currently visible preferences window
     
    -    def __init__(self, parent, account, app):
    +    def __init__(self, parent, app):
             """
             :param parent: parent object of the PreferencesWindow.
             :parent type: QWidget
     
    -        :param account: the user or provider
    -        :type account: Account
    -
             :param app: the current App object
             :type app: App
             """
             QtGui.QDialog.__init__(self, parent)
     
    -        self.account = account
             self.app = app
     
             self.ui = Ui_Preferences()
             self.ui.setupUi(self)
     
    -        self.ui.close_button.clicked.connect(self.close)
    -        self.ui.account_label.setText(account.address)
    -
    -        self.app.service_selection_changed.connect(self._update_icons)
    +        self._account_page = None
    +        self._vpn_page = None
    +        self._email_page = None
     
             self._add_icons()
    -        self._add_pages()
    -        self._update_icons(self.account, self.account.services())
    +        self._set_account(app.current_account())
    +        self._setup_connections()
     
             # only allow a single preferences window at a time.
             if PreferencesWindow._current_window is not None:
                 PreferencesWindow._current_window.close()
             PreferencesWindow._current_window = self
     
    +    def _set_account(self, account):
    +        """
    +        Initially sets, or resets, the currently viewed account.
    +        The account might not represent an authenticated user, but
    +        just a domain.
    +        """
    +        self.ui.account_label.setText(account.address)
    +        self._add_pages(account)
    +        self._update_icons(account)
    +        self.ui.pages_widget.setCurrentIndex(0)
    +        self.ui.nav_widget.setCurrentRow(0)
    +
         def _add_icons(self):
             """
             Adds all the icons for the different configuration categories.
    @@ -114,22 +118,71 @@ class PreferencesWindow(QtGui.QDialog):
             email_item.setSizeHint(QtCore.QSize(98, 56))
             self._email_item = email_item
     
    -        self.ui.nav_widget.currentItemChanged.connect(self._change_page)
    -        self.ui.nav_widget.setCurrentRow(0)
    -
    -    def _add_pages(self):
    +    def _add_pages(self, account):
             """
             Adds the pages for the different configuration categories.
             """
    -        self._account_page = PreferencesAccountPage(
    -            self, self.account, self.app)
    -        self._vpn_page = PreferencesVpnPage(self, self.account, self.app)
    -        self._email_page = PreferencesEmailPage(self, self.account, self.app)
    -
    +        self._remove_pages() # in case different account was loaded.
    +
    +        # load placeholder widgets if the page should not be loaded.
    +        # the order of the pages is important, and must match the order
    +        # of the nav_widget icons.
    +        self._account_page = PreferencesAccountPage(self, account, self.app)
    +        if account.has_eip():
    +            self._vpn_page = PreferencesVpnPage(self, account, self.app)
    +        else:
    +            self._vpn_page = PreferencesPage(self)
    +        if account.has_email():
    +            self._email_page = PreferencesEmailPage(self, account, self.app)
    +        else:
    +            self._email_page = PreferencesPage(self)
             self.ui.pages_widget.addWidget(self._account_page)
             self.ui.pages_widget.addWidget(self._vpn_page)
             self.ui.pages_widget.addWidget(self._email_page)
     
    +    def _remove_pages(self):
    +        # deleteLater does not seem to cascade to items in stackLayout
    +        # (even with QtCore.Qt.WA_DeleteOnClose attribute).
    +        # so, here we call deleteLater() explicitly.
    +        if self._account_page is not None:
    +            self.ui.pages_widget.removeWidget(self._account_page)
    +            self._account_page.teardown_connections()
    +            self._account_page.deleteLater()
    +        if self._vpn_page is not None:
    +            self.ui.pages_widget.removeWidget(self._vpn_page)
    +            self._vpn_page.teardown_connections()
    +            self._vpn_page.deleteLater()
    +        if self._email_page is not None:
    +            self.ui.pages_widget.removeWidget(self._email_page)
    +            self._email_page.teardown_connections()
    +            self._email_page.deleteLater()
    +
    +    def _setup_connections(self):
    +        """
    +        setup signal connections
    +        """
    +        self.ui.nav_widget.currentItemChanged.connect(self._change_page)
    +        self.ui.close_button.clicked.connect(self.close)
    +        self.app.service_selection_changed.connect(self._update_icons)
    +        sig = self.app.signaler
    +        sig.srp_auth_ok.connect(self._login_status_changed)
    +        sig.srp_logout_ok.connect(self._login_status_changed)
    +        sig.srp_status_logged_in.connect(self._update_account)
    +        sig.srp_status_not_logged_in.connect(self._update_account)
    +
    +    def _teardown_connections(self):
    +        """
    +        clean up signal connections
    +        """
    +        self.ui.nav_widget.currentItemChanged.disconnect(self._change_page)
    +        self.ui.close_button.clicked.disconnect(self.close)
    +        self.app.service_selection_changed.disconnect(self._update_icons)
    +        sig = self.app.signaler
    +        sig.srp_auth_ok.disconnect(self._login_status_changed)
    +        sig.srp_logout_ok.disconnect(self._login_status_changed)
    +        sig.srp_status_logged_in.disconnect(self._update_account)
    +        sig.srp_status_not_logged_in.disconnect(self._update_account)
    +
         #
         # Slots
         #
    @@ -144,13 +197,8 @@ class PreferencesWindow(QtGui.QDialog):
             Close this dialog and destroy it.
             """
             PreferencesWindow._current_window = None
    -
    -        # deleteLater does not seem to cascade to items in stackLayout
    -        # (even with QtCore.Qt.WA_DeleteOnClose attribute).
    -        # so, here we call deleteLater() explicitly:
    -        self._account_page.deleteLater()
    -        self._vpn_page.deleteLater()
    -        self._email_page.deleteLater()
    +        self._teardown_connections();
    +        self._remove_pages();
             self.deleteLater()
     
         def _change_page(self, current, previous):
    @@ -170,17 +218,24 @@ class PreferencesWindow(QtGui.QDialog):
                 current = previous
             self.ui.pages_widget.setCurrentIndex(self.ui.nav_widget.row(current))
     
    -    def _update_icons(self, account, services):
    +    def _update_icons(self, account):
             """
             TRIGGERS:
                 self.app.service_selection_changed
     
             Change which icons are visible.
             """
    -        if account != self.account:
    -            return
    +        self._vpn_item.setHidden(not account.has_eip())
    +        self._email_item.setHidden(not account.has_email())
    +
    +    def _login_status_changed(self):
    +        """
    +        Triggered by signal srp_auth_ok, srp_logout_ok
    +        """
    +        self.app.backend.user_get_logged_in_status()
     
    -        if HAS_EIP:
    -            self._vpn_item.setHidden(EIP_SERVICE not in services)
    -        # self._email_item.setHidden(not MX_SERVICE in services)
    -        # ^^ disable email for now, there is nothing there yet.
    +    def _update_account(self):
    +        """
    +        Triggered by get srp_status_logged_in, srp_status_not_logged_in
    +        """
    +        self._set_account(self.app.current_account())
    diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui
    index 5e30ea57..51cad0a1 100644
    --- a/src/leap/bitmask/gui/ui/preferences.ui
    +++ b/src/leap/bitmask/gui/ui/preferences.ui
    @@ -6,8 +6,8 @@
        
         0
         0
    -    520
    -    439
    +    630
    +    500
        
       
    @@ -60,6 +60,24 @@ 16777215 + + background: palette(base); border: 1px solid palette(dark); border-radius: 2px; + + + QFrame::StyledPanel + + + QFrame::Plain + + + 1 + + + 0 + + + Qt::ScrollBarAlwaysOff + 32 diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 7cc5bb3c..5f83426b 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -6,13 +6,572 @@ 0 0 - 400 - 300 + 526 + 605 Form + + + 0 + + + 0 + + + + + 2 + + + + + + + + + + Tab 1 + + + + + + Thunderbird Configuration + + + + + + thunderbird information + + + true + + + + + + + + + + + 0 + 0 + + + + Mail Client Configuration + + + + + + mail client information + + + true + + + + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + IMAP + + + + + + + Username + + + + + + + SMTP + + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + + + true + + + + + + + Password + + + + + + + true + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Tab 2 + + + + + + + + + Public Key + + + + + + + + 0 + 0 + + + + + Courier + + + + QPlainTextEdit::NoWrap + + + true + + + + + + + + 0 + 42 + + + + + 16777215 + 48 + + + + + Courier + 50 + false + + + + false + + + + + + QPlainTextEdit::NoWrap + + + true + + + + + + + + + + Expiration + + + + + + + Fingerprint + + + + + + + true + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + Address + + + + + + + true + + + + + + + + + Export Private Key + + + + + + + Import Private Key + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + uid_edit + fp_label + uid_label + expiration_edit + expiration_label + fingerprint_edit + public_key_edit + pub_label + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Page + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideRight + + + true + + + + Email + + + + + Key ID + + + + + + + + + + + + this message should be hidden + + + +
    -- cgit v1.2.3 From 5b90ad3552025436edb40665203ab98eceaa065b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 19 Apr 2016 12:07:09 -0400 Subject: pep8/flake8 --- src/leap/bitmask/core/service.py | 4 +-- src/leap/bitmask/gui/account.py | 1 + src/leap/bitmask/gui/app.py | 4 +-- src/leap/bitmask/gui/mainwindow.py | 8 +++--- src/leap/bitmask/gui/preferences_account_page.py | 1 - src/leap/bitmask/gui/preferences_email_page.py | 36 ++++++++++++------------ src/leap/bitmask/gui/preferences_page.py | 6 ++-- src/leap/bitmask/gui/preferences_vpn_page.py | 2 +- src/leap/bitmask/gui/preferenceswindow.py | 7 +++-- src/leap/bitmask/gui/statemachines.py | 5 +++- src/leap/bitmask/services/eip/vpnprocess.py | 1 - 11 files changed, 39 insertions(+), 36 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 87349ef6..13c8864a 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -29,7 +29,7 @@ from leap.bitmask.core import _zmq from leap.bitmask.core import websocket from leap.bonafide.service import BonafideService from leap.common.events import server as event_server -#from leap.vpn import EIPService +# from leap.vpn import EIPService class BitmaskBackend(configurable.ConfigurableService): @@ -96,7 +96,7 @@ class BitmaskBackend(configurable.ConfigurableService): def init_eip(self): # FIXME -- land EIP into leap.vpn pass - #self._maybe_start_service('eip', EIPService) + # self._maybe_start_service('eip', EIPService) def init_zmq(self): zs = _zmq.ZMQServerService(self) diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py index 5e43c8fe..b8b9509a 100644 --- a/src/leap/bitmask/gui/account.py +++ b/src/leap/bitmask/gui/account.py @@ -22,6 +22,7 @@ from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.services import EIP_SERVICE, MX_SERVICE from leap.bitmask._components import HAS_EIP, HAS_MAIL + class Account(): def __init__(self, username, domain): diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py index e3a4d7fe..1011454e 100644 --- a/src/leap/bitmask/gui/app.py +++ b/src/leap/bitmask/gui/app.py @@ -60,8 +60,8 @@ class App(QtGui.QWidget): def current_account(self): """ - Alas, the only definitive account information is buried in the memory of - QT widgets. + Alas, the only definitive account information is buried in the memory + of QT widgets. :returns: an object representing the current user account. :rtype: Account diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index ca14e631..daf49eb6 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -154,11 +154,11 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._providers = Providers(self.ui.cmbProviders) ## - ## tmphack: important state information about the application is stored - ## in widgets. Rather than rewrite the UI, for now we simulate this - ## info being stored in an application object: + # tmphack: important state information about the application is stored + # in widgets. Rather than rewrite the UI, for now we simulate this + # info being stored in an application object: ## - self.app.login_state = self._login_widget._state + self.app.login_state = self._login_widget._state self.app.providers_widget = self._providers # Qt Signal Connections ##################################### diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index c175c42b..141523c8 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -69,7 +69,6 @@ class PreferencesAccountPage(PreferencesPage): self.app.signaler.prov_get_supported_services.connect( self._load_services) - def teardown_connections(self): """ disconnect signals diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 50f244fb..8211aeb8 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -18,8 +18,6 @@ Widget for "email" preferences """ from PySide import QtCore, QtGui -from datetime import datetime - from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage from leap.bitmask.gui.preferences_page import PreferencesPage @@ -27,6 +25,7 @@ from leap.mail.imap.service.imap import IMAP_PORT logger = get_logger() + class PreferencesEmailPage(PreferencesPage): def __init__(self, parent, account, app): @@ -46,11 +45,11 @@ class PreferencesEmailPage(PreferencesPage): # the only way to set the tab titles is to re-add them: self.ui.email_tabs.addTab(self.ui.config_tab, - self.tr("Mail Client")) + self.tr("Mail Client")) self.ui.email_tabs.addTab(self.ui.my_key_tab, - self.tr("My Key")) + self.tr("My Key")) self.ui.email_tabs.addTab(self.ui.other_keys_tab, - self.tr("Other Keys")) + self.tr("Other Keys")) # set mail client configuration help text lang = QtCore.QLocale.system().name().replace('_', '-') @@ -61,7 +60,7 @@ class PreferencesEmailPage(PreferencesPage): "For Thunderbird, you can use the Bitmask extension. " "Search for \"Bitmask\" in the add-on manager or " "download it from addons.mozilla.org.".format( - thunderbird_extension_url))) + thunderbird_extension_url))) self.ui.mail_client_label.setText(self.tr( "Alternatively, you can manually configure your mail client to " "use Bitmask Email with these options:")) @@ -71,7 +70,6 @@ class PreferencesEmailPage(PreferencesPage): self.setup_connections() - def setup_connections(self): """ connect signals @@ -106,7 +104,7 @@ class PreferencesEmailPage(PreferencesPage): self.ui.message_label.setText( self.tr('You must be logged in to edit email settings.')) else: - self.ui.import_button.setVisible(False) # hide this until working + self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) smtp_port = 2013 @@ -128,7 +126,7 @@ class PreferencesEmailPage(PreferencesPage): Set the current user's key details into the gui. """ self.ui.fingerprint_edit.setPlainText( - self._format_fingerprint(details["fingerprint"])) + self._format_fingerprint(details["fingerprint"])) self.ui.expiration_edit.setText(details["expiry_date"]) self.ui.uid_edit.setText(" ".join(details["uids"])) self.ui.public_key_edit.setPlainText(details["key_data"]) @@ -139,8 +137,8 @@ class PreferencesEmailPage(PreferencesPage): produces, wrapped to two lines. """ fp = fingerprint.upper() - fp_list = [fp[i:i+4] for i in range(0, len(fp), 4)] - fp_wrapped = " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10]) + fp_list = [fp[i:i + 4] for i in range(0, len(fp), 4)] + fp_wrapped = " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10]) return fp_wrapped def _export_keys(self): @@ -156,8 +154,8 @@ class PreferencesEmailPage(PreferencesPage): if not file_name.endswith('.pem'): file_name += '.pem' self.app.backend.keymanager_export_keys( - username=self.account.address, - filename=file_name) + username=self.account.address, + filename=file_name) else: logger.debug('Export canceled by the user.') @@ -186,9 +184,9 @@ class PreferencesEmailPage(PreferencesPage): "Export canceled.")) def _import_keys(self): - """ - not yet supported - """ + """ + not yet supported + """ def _keymanager_keys_list(self, keys): """ @@ -203,5 +201,7 @@ class PreferencesEmailPage(PreferencesPage): for key in keys: row = self.ui.keys_table.rowCount() self.ui.keys_table.insertRow(row) - self.ui.keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address)) - self.ui.keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.fingerprint)) + self.ui.keys_table.setItem( + row, 0, QtGui.QTableWidgetItem(key.address)) + self.ui.keys_table.setItem( + row, 1, QtGui.QTableWidgetItem(key.fingerprint)) diff --git a/src/leap/bitmask/gui/preferences_page.py b/src/leap/bitmask/gui/preferences_page.py index c75b4991..a5d811f9 100644 --- a/src/leap/bitmask/gui/preferences_page.py +++ b/src/leap/bitmask/gui/preferences_page.py @@ -14,10 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ -base class for preference pages +Base class for preference pages """ -from PySide import QtCore, QtGui +from PySide import QtGui + class PreferencesPage(QtGui.QWidget): @@ -47,4 +48,3 @@ class PreferencesPage(QtGui.QWidget): disconnect signals must be overridden by subclass """ - diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py index fc15340f..87b86c4e 100644 --- a/src/leap/bitmask/gui/preferences_vpn_page.py +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -20,10 +20,10 @@ Widget for "vpn" preferences from PySide import QtCore, QtGui from leap.bitmask.gui.ui_preferences_vpn_page import Ui_PreferencesVpnPage -from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.flashable import Flashable from leap.bitmask.gui.preferences_page import PreferencesPage + class PreferencesVpnPage(PreferencesPage, Flashable): """ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 1facba69..82dc8d77 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -29,6 +29,7 @@ from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage logger = get_logger() + class PreferencesWindow(QtGui.QDialog): """ @@ -122,7 +123,7 @@ class PreferencesWindow(QtGui.QDialog): """ Adds the pages for the different configuration categories. """ - self._remove_pages() # in case different account was loaded. + self._remove_pages() # in case different account was loaded. # load placeholder widgets if the page should not be loaded. # the order of the pages is important, and must match the order @@ -197,8 +198,8 @@ class PreferencesWindow(QtGui.QDialog): Close this dialog and destroy it. """ PreferencesWindow._current_window = None - self._teardown_connections(); - self._remove_pages(); + self._teardown_connections() + self._remove_pages() self.deleteLater() def _change_page(self, current, previous): diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index ab48b756..92c5431d 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -40,6 +40,7 @@ class SignallingState(QState): """ A state that emits a custom signal on entry. """ + def __init__(self, signal, parent=None, name=None): """ Initializer. @@ -134,6 +135,7 @@ class States(object): class CompositeEvent(QtCore.QEvent): + def __init__(self): super(CompositeEvent, self).__init__( QtCore.QEvent.Type(self.ID)) @@ -174,6 +176,7 @@ class Events(QtCore.QObject): A Wrapper object for containing the events that will be posted to a composite state machine. """ + def __init__(self, parent=None): """ Initializes the QObject with the given parent. @@ -289,6 +292,7 @@ class ConnectionMachineBuilder(object): """ Builder class for state machines made from LEAPConnections. """ + def __init__(self, connection): """ :param connection: an instance of a concrete LEAPConnection @@ -352,7 +356,6 @@ class ConnectionMachineBuilder(object): :rtype: QStateMachine """ # TODO split this method in smaller utility functions. - parent = kwargs.get('parent', None) # 1. create machine machine = CompositeMachine() diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index de8d92f3..580bd572 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -23,7 +23,6 @@ import shutil import socket import subprocess import sys -import time from itertools import chain, repeat -- cgit v1.2.3 From 9a0568c18ed38558a481d5f6e4be12c68f9870a6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 19 Apr 2016 13:14:54 -0400 Subject: [feature] enable webmail from the mail preferences window this box is only visible when the HAS_PIXELATED flag is True, that means we have pixelated-user-agent and pixelated-www in the environment. - Releases: 0.9.2 --- src/leap/bitmask/config/leapsettings.py | 2 +- src/leap/bitmask/gui/preferences_email_page.py | 19 ++++++++++++++ src/leap/bitmask/gui/ui/preferences_email_page.ui | 30 +++++++++++++++++++++-- 3 files changed, 48 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 01900484..075be8a7 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -360,4 +360,4 @@ class LeapSettings(object): def set_pixelmail_enabled(self, enabled): leap_assert_type(enabled, bool) - self._settings.setvalue(self.PIXELMAIL_KEY, enabled) + self._settings.setValue(self.PIXELMAIL_KEY, enabled) diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 8211aeb8..b8633f07 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -18,11 +18,14 @@ Widget for "email" preferences """ from PySide import QtCore, QtGui +from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage from leap.bitmask.gui.preferences_page import PreferencesPage +from leap.bitmask.pix import HAS_PIXELATED from leap.mail.imap.service.imap import IMAP_PORT + logger = get_logger() @@ -40,6 +43,7 @@ class PreferencesEmailPage(PreferencesPage): :type app: App """ PreferencesPage.__init__(self, parent, account, app) + self.settings = LeapSettings() self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) @@ -65,6 +69,16 @@ class PreferencesEmailPage(PreferencesPage): "Alternatively, you can manually configure your mail client to " "use Bitmask Email with these options:")) + self.ui.webmail_label.setText(self.tr( + "This distribution of Bitmask ships an experimental integration " + "of the Pixelated " + "Webmail. It is not stable yet, but you can enable it to help " + "beta-testing it. (Needs restart!)")) + webmail_enabled = self.settings.get_pixelmail_enabled() + self.ui.webmail_checkbox.setChecked(webmail_enabled) + if not HAS_PIXELATED: + self.ui.webmail_box.setVisible(False) + self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) @@ -81,6 +95,7 @@ class PreferencesEmailPage(PreferencesPage): self._keymanager_export_error) self.ui.import_button.clicked.connect(self._import_keys) self.ui.export_button.clicked.connect(self._export_keys) + self.ui.webmail_checkbox.stateChanged.connect(self._toggle_webmail) def teardown_connections(self): """ @@ -205,3 +220,7 @@ class PreferencesEmailPage(PreferencesPage): row, 0, QtGui.QTableWidgetItem(key.address)) self.ui.keys_table.setItem( row, 1, QtGui.QTableWidgetItem(key.fingerprint)) + + def _toggle_webmail(self, state): + value = True if state == QtCore.Qt.Checked else False + self.settings.set_pixelmail_enabled(value) diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 5f83426b..22d690d9 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -23,7 +23,7 @@ - 2 + 0 @@ -35,7 +35,7 @@ Tab 1 - + @@ -332,6 +332,32 @@ + + + + Webmail Configuration + + + + + + Enable Bitmask Webmail (beta) + + + + + + + webmail info + + + true + + + + + + -- cgit v1.2.3 From a68bbe5e69a3481ede78f518b1637761e8f8cd01 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 20 Apr 2016 12:44:15 -0300 Subject: [bug] let the failure propagate The failure was processed in start_incoming_mail_service what will make it return a None when an IncomingMail object was expected. If we propagate the failure it can be treated properly by the IMAPController. - Related: #8051 --- src/leap/bitmask/services/mail/imap.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 2f000b2a..7875a4af 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -94,5 +94,4 @@ def start_incoming_mail_service(keymanager, soledad, userid): acc = Account(soledad, userid) d = acc.callWhenReady(lambda _: acc.get_collection_by_mailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) - d.addErrback(log.err) return d -- cgit v1.2.3 From b6bf547e3a8b96b5fcdba739ed4be74bf040a7ed Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 25 Apr 2016 17:25:36 -0400 Subject: [refactor] move the pixelated panel upwards --- src/leap/bitmask/gui/preferences_email_page.py | 7 +-- src/leap/bitmask/gui/qt_browser.py | 2 +- src/leap/bitmask/gui/ui/preferences_email_page.ui | 54 +++++++++++------------ 3 files changed, 32 insertions(+), 31 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index b8633f07..f6d6f036 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -71,9 +71,10 @@ class PreferencesEmailPage(PreferencesPage): self.ui.webmail_label.setText(self.tr( "This distribution of Bitmask ships an experimental integration " - "of the Pixelated " - "Webmail. It is not stable yet, but you can enable it to help " - "beta-testing it. (Needs restart!)")) + "of Pixelated " + "Mail. Note: at the current state, anyone with access to " + "your device can read your mail without authentication, " + "by opening a browser pointing to http://localhost:9090 ")) webmail_enabled = self.settings.get_pixelmail_enabled() self.ui.webmail_checkbox.setChecked(webmail_enabled) if not HAS_PIXELATED: diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index b75bfb64..c62e7770 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -33,7 +33,7 @@ class PixelatedWindow(QtGui.QDialog): layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.view) self.setLayout(layout) - self.setWindowTitle('Bitmask/Pixelated WebMail') + self.setWindowTitle('Bitmask Mail') def load_app(self): self.view.load(QtCore.QUrl(PIXELATED_URI)) diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 22d690d9..87e7121d 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -36,6 +36,32 @@ Tab 1 + + + + Bitmask Mail Configuration + + + + + + Enable Bitmask Mail (needs restart) + + + + + + + webmail info + + + true + + + + + + @@ -64,7 +90,7 @@ - Mail Client Configuration + Other Mail Clients Configuration @@ -332,32 +358,6 @@ - - - - Webmail Configuration - - - - - - Enable Bitmask Webmail (beta) - - - - - - - webmail info - - - true - - - - - - -- cgit v1.2.3 From 5a1dd49debdcd9a1ce0568217e9411d1e45a3cad Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 25 Apr 2016 20:24:04 -0300 Subject: [bug] represent keys correctly in preference window --- src/leap/bitmask/backend/components.py | 10 +++++----- src/leap/bitmask/gui/preferences_email_page.py | 6 ++++-- src/leap/bitmask/gui/ui/preferences_email_page.ui | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 3192e1c4..ba64fd65 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -994,14 +994,14 @@ class Keymanager(object): d.addCallback(export) d.addErrback(log_error) + @defer.inlineCallbacks def list_keys(self): """ List all the keys stored in the local DB. """ - d = self._keymanager_proxy.get_all_keys() - d.addCallback( - lambda keys: - self._signaler.signal(self._signaler.keymanager_keys_list, keys)) + keys = yield self._keymanager_proxy.get_all_keys() + keydicts = [dict(key) for key in keys] + self._signaler.signal(self._signaler.keymanager_keys_list, keydicts) def get_key_details(self, username): """ @@ -1009,7 +1009,7 @@ class Keymanager(object): """ def signal_details(public_key): self._signaler.signal(self._signaler.keymanager_key_details, - public_key.get_dict()) + dict(public_key)) d = self._keymanager_proxy.get_key(username, openpgp.OpenPGPKey) diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index f6d6f036..7186450d 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -90,6 +90,8 @@ class PreferencesEmailPage(PreferencesPage): connect signals """ self.app.signaler.keymanager_key_details.connect(self._key_details) + self.app.signaler.keymanager_keys_list.connect( + self._keymanager_keys_list) self.app.signaler.keymanager_export_ok.connect( self._keymanager_export_ok) self.app.signaler.keymanager_export_error.connect( @@ -218,9 +220,9 @@ class PreferencesEmailPage(PreferencesPage): row = self.ui.keys_table.rowCount() self.ui.keys_table.insertRow(row) self.ui.keys_table.setItem( - row, 0, QtGui.QTableWidgetItem(key.address)) + row, 0, QtGui.QTableWidgetItem(" ".join(key["uids"]))) self.ui.keys_table.setItem( - row, 1, QtGui.QTableWidgetItem(key.fingerprint)) + row, 1, QtGui.QTableWidgetItem(key["fingerprint"])) def _toggle_webmail(self, state): value = True if state == QtCore.Qt.Checked else False diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 87e7121d..08feb7b0 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -581,7 +581,7 @@ - Key ID + Fingerprint -- cgit v1.2.3 From 68096fa81eebb07165c27648ab804d9e1c695f8e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 26 Apr 2016 23:07:42 -0400 Subject: [style] fix typo: Webmail -> Mail --- src/leap/bitmask/gui/ui/mainwindow.ui | 2 +- src/leap/bitmask/gui/ui/preferences_email_page.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 976c0c0a..0dd0b891 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -373,7 +373,7 @@ - Bitmask Webmail + Bitmask Mail diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index 87e7121d..1101d7ba 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -39,7 +39,7 @@ - Bitmask Mail Configuration + Bitmask Mail -- cgit v1.2.3 From da45ed15190ea0f9c7c38b6312239f27664545a4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 28 Apr 2016 15:46:13 -0400 Subject: [bug] do not force autobahn dependency yet websockets interface is not mature enough yet, make this dependency optional, for the case the user actively enables it. - Releases: 0.9.2 --- src/leap/bitmask/core/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 13c8864a..ceda6353 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -26,7 +26,6 @@ from leap.bitmask import __version__ from leap.bitmask.core import configurable from leap.bitmask.core import mail_services from leap.bitmask.core import _zmq -from leap.bitmask.core import websocket from leap.bonafide.service import BonafideService from leap.common.events import server as event_server # from leap.vpn import EIPService @@ -103,6 +102,7 @@ class BitmaskBackend(configurable.ConfigurableService): zs.setServiceParent(self) def init_web(self): + from leap.bitmask.core import websocket ws = websocket.WebSocketsDispatcherService(self) ws.setServiceParent(self) -- cgit v1.2.3 From 928f547a1d1235306056be1d81e8400d4d77ecce Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 30 Apr 2016 12:15:16 -0400 Subject: [bug] fix hardcoded paths --- src/leap/bitmask/core/mail_services.py | 4 ++-- src/leap/bitmask/core/service.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index 6472bdc2..3495aa39 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -302,7 +302,7 @@ class KeymanagerContainer(Container): class KeymanagerService(HookableService): - def __init__(self, basedir='~/.config/leap'): + def __init__(self, basedir=DEFAULT_BASEDIR): service.Service.__init__(self) self._basedir = basedir @@ -507,7 +507,7 @@ class SMTPService(service.Service): name = 'smtp' def __init__(self, soledad_sessions, keymanager_sessions, sendmail_opts, - basedir='~/.config/leap'): + basedir=DEFAULT_BASEDIR): self._basedir = os.path.expanduser(basedir) port, factory = smtp.run_service( diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index ceda6353..ddd86155 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -33,7 +33,7 @@ from leap.common.events import server as event_server class BitmaskBackend(configurable.ConfigurableService): - def __init__(self, basedir='~/.config/leap'): + def __init__(self, basedir=configurable.DEFAULT_BASEDIR): configurable.ConfigurableService.__init__(self, basedir) -- cgit v1.2.3 From 08da5b11103cdd132c3ac4110ba42fcc8510a78b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 30 Apr 2016 12:15:58 -0400 Subject: [refactor] pass backend to core service --- src/leap/bitmask/core/flags.py | 1 + src/leap/bitmask/core/launcher.py | 7 ++++++- src/leap/bitmask/core/service.py | 13 ++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/leap/bitmask/core/flags.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/core/flags.py b/src/leap/bitmask/core/flags.py new file mode 100644 index 00000000..9a40c70c --- /dev/null +++ b/src/leap/bitmask/core/flags.py @@ -0,0 +1 @@ +BACKEND = 'default' diff --git a/src/leap/bitmask/core/launcher.py b/src/leap/bitmask/core/launcher.py index b2319077..b8916a1e 100644 --- a/src/leap/bitmask/core/launcher.py +++ b/src/leap/bitmask/core/launcher.py @@ -17,17 +17,22 @@ """ Run bitmask daemon. """ -from twisted.scripts.twistd import run from os.path import join from sys import argv +from twisted.scripts.twistd import run + from leap.bitmask.util import here from leap.bitmask import core +from leap.bitmask.core import flags def run_bitmaskd(): # TODO --- configure where to put the logs... (get --logfile, --logdir # from the bitmask_cli + for (index, arg) in enumerate(argv): + if arg == '--backend': + flags.BACKEND = argv[index + 1] argv[1:] = [ '-y', join(here(core), "bitmaskd.tac"), '--pidfile', '/tmp/bitmaskd.pid', diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index ddd86155..fca51048 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -17,6 +17,7 @@ """ Bitmask-core Service. """ +import json import resource from twisted.internet import reactor @@ -26,6 +27,7 @@ from leap.bitmask import __version__ from leap.bitmask.core import configurable from leap.bitmask.core import mail_services from leap.bitmask.core import _zmq +from leap.bitmask.core import flags from leap.bonafide.service import BonafideService from leap.common.events import server as event_server # from leap.vpn import EIPService @@ -126,17 +128,18 @@ class BitmaskBackend(configurable.ConfigurableService): # we may want to make this tuple a class member services = ('soledad', 'keymanager', 'mail', 'eip') - status_messages = [] + status = {} for name in services: - status = 'stopped' + _status = 'stopped' try: if self.getServiceNamed(name).running: - status = "running" + _status = 'running' except KeyError: pass - status_messages.append("[{}: {}]".format(name, status)) + status[name] = _status + status['backend'] = flags.BACKEND - return " ".join(status_messages) + return json.dumps(status) def do_version(self): version = __version__ -- cgit v1.2.3 From 1173e77cb8d635936c9730ba4ad8b88b24ad1be2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 1 May 2016 11:09:07 -0400 Subject: [feature] pluggable backends and api registry the idea behind this mechanism (partially implemented for that) is to be able to check the backend output against some type annotations. We want to be able to detect if a given backend (real services or authoritative mocks) have diverged from what's specified in the API annotations. --- src/leap/bitmask/cli/bitmask_cli.py | 2 +- src/leap/bitmask/core/api.py | 54 +++++++ src/leap/bitmask/core/api_contract.py | 40 +++++ src/leap/bitmask/core/dispatcher.py | 286 +++++++++++++++++++++------------ src/leap/bitmask/core/dummy.py | 80 +++++++++ src/leap/bitmask/core/mail_services.py | 10 +- src/leap/bitmask/core/service.py | 86 ++++++---- 7 files changed, 420 insertions(+), 138 deletions(-) create mode 100644 src/leap/bitmask/core/api.py create mode 100644 src/leap/bitmask/core/api_contract.py create mode 100644 src/leap/bitmask/core/dummy.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/cli/bitmask_cli.py b/src/leap/bitmask/cli/bitmask_cli.py index c5bb1b15..c2b1ba71 100755 --- a/src/leap/bitmask/cli/bitmask_cli.py +++ b/src/leap/bitmask/cli/bitmask_cli.py @@ -325,7 +325,7 @@ def send_command(cli): s = get_zmq_connection() - d = s.sendMsg(*data, timeout=20) + d = s.sendMsg(*data, timeout=60) d.addCallback(cb) d.addCallback(lambda x: reactor.stop()) d.addErrback(timeout_handler) diff --git a/src/leap/bitmask/core/api.py b/src/leap/bitmask/core/api.py new file mode 100644 index 00000000..9f3725dc --- /dev/null +++ b/src/leap/bitmask/core/api.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# api.py +# Copyright (C) 2016 LEAP Encryption Acess Project +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Registry for the public API for the Bitmask Backend. +""" +from collections import OrderedDict + +registry = OrderedDict() + + +class APICommand(type): + """ + A metaclass to keep a global registry of all the methods that compose the + public API for the Bitmask Backend. + """ + def __init__(cls, name, bases, attrs): + for key, val in attrs.iteritems(): + properties = getattr(val, 'register', None) + label = getattr(cls, 'label', None) + if label: + name = label + if properties is not None: + registry['%s.%s' % (name, key)] = properties + + +def register_method(*args): + """ + This method gathers info about all the methods that are supposed to + compose the public API to communicate with the backend. + + It sets up a register property for any method that uses it. + A type annotation is supposed to be in this property. + The APICommand metaclass collects these properties of the methods and + stores them in the global api_registry object, where they can be + introspected at runtime. + """ + def decorator(f): + f.register = tuple(args) + return f + return decorator diff --git a/src/leap/bitmask/core/api_contract.py b/src/leap/bitmask/core/api_contract.py new file mode 100644 index 00000000..86b600c1 --- /dev/null +++ b/src/leap/bitmask/core/api_contract.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# api_contract.py +# Copyright (C) 2016 LEAP Encryption Acess Project +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Display a human-readable representation of the methods that compound the public +api for Bitmask Core. + +The values are meant to be type annotations. +""" + +if __name__ == "__main__": + from leap.bitmask.core.service import BitmaskBackend + from leap.bitmask.core import api + backend = BitmaskBackend() + + print '========= Bitmask Core API ==================' + print + + for key in api.registry: + human_key = key.replace('do_', '').lower() + value = api.registry[key] + + print("{}:\t\t{}".format( + human_key, + ' '.join([x for x in value]))) + print + print '=============================================' diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py index 648cbe9b..e7c961fd 100644 --- a/src/leap/bitmask/core/dispatcher.py +++ b/src/leap/bitmask/core/dispatcher.py @@ -22,154 +22,214 @@ import json from twisted.internet import defer from twisted.python import failure, log +from .api import APICommand, register_method -# TODO implement sub-classes to dispatch subcommands (user, mail). +class SubCommand(object): -class CommandDispatcher(object): + __metaclass__ = APICommand - def __init__(self, core): + def dispatch(self, service, *parts, **kw): + subcmd = parts[1] - self.core = core + _method = getattr(self, 'do_' + subcmd.upper(), None) + if not _method: + raise RuntimeError('No such subcommand') + return _method(service, *parts, **kw) - def _get_service(self, name): - try: - return self.core.getServiceNamed(name) - except KeyError: - return None +class UserCmd(SubCommand): - def dispatch(self, msg): - cmd = msg[0] + label = 'user' - _method = getattr(self, 'do_' + cmd.upper(), None) + @register_method("{'srp_token': unicode, 'uuid': unicode}") + def do_AUTHENTICATE(self, bonafide, *parts): + user, password = parts[2], parts[3] + d = defer.maybeDeferred(bonafide.do_authenticate, user, password) + return d - if not _method: - return defer.fail(failure.Failure(RuntimeError('No such command'))) + @register_method("{'signup': 'ok', 'user': str}") + def do_SIGNUP(self, bonafide, *parts): + user, password = parts[2], parts[3] + d = defer.maybeDeferred(bonafide.do_signup, user, password) + return d - return defer.maybeDeferred(_method, *msg) + @register_method("{'logout': 'ok'}") + def do_LOGOUT(self, bonafide, *parts): + user, password = parts[2], parts[3] + d = defer.maybeDeferred(bonafide.do_logout, user, password) + return d - def do_STATS(self, *parts): - return _format_result(self.core.do_stats()) + @register_method('str') + def do_ACTIVE(self, bonafide, *parts): + d = defer.maybeDeferred(bonafide.do_get_active_user) + return d - def do_VERSION(self, *parts): - return _format_result(self.core.do_version()) - def do_STATUS(self, *parts): - return _format_result(self.core.do_status()) +class EIPCmd(SubCommand): - def do_SHUTDOWN(self, *parts): - return _format_result(self.core.do_shutdown()) + label = 'eip' - def do_USER(self, *parts): + @register_method('dict') + def do_ENABLE(self, service, *parts): + d = service.do_enable_service(self.label) + return d - subcmd = parts[1] - user, password = parts[2], parts[3] + @register_method('dict') + def do_DISABLE(self, service, *parts): + d = service.do_disable_service(self.label) + return d - bf = self._get_service('bonafide') + @register_method('dict') + def do_STATUS(self, eip, *parts): + d = eip.do_status() + return d - if subcmd == 'authenticate': - d = bf.do_authenticate(user, password) + @register_method('dict') + def do_START(self, eip, *parts): + # TODO --- attempt to get active provider + # TODO or catch the exception and send error + provider = parts[2] + d = eip.do_start(provider) + return d - elif subcmd == 'signup': - d = bf.do_signup(user, password) + @register_method('dict') + def do_STOP(self, eip, *parts): + d = eip.do_stop() + return d - elif subcmd == 'logout': - d = bf.do_logout(user, password) - elif subcmd == 'active': - d = bf.do_get_active_user() +class MailCmd(SubCommand): - d.addCallbacks(_format_result, _format_error) + label = 'mail' + + @register_method('dict') + def do_ENABLE(self, service, *parts): + d = service.do_enable_service(self.label) return d - def do_EIP(self, *parts): + @register_method('dict') + def do_DISABLE(self, service, *parts): + d = service.do_disable_service(self.label) + return d - subcmd = parts[1] - eip_label = 'eip' + @register_method('dict') + def do_STATUS(self, mail, *parts): + d = mail.do_status() + return d - if subcmd == 'enable': - return _format_result( - self.core.do_enable_service(eip_label)) + @register_method('dict') + def do_GET_IMAP_TOKEN(self, mail, *parts): + d = mail.get_imap_token() + return d - eip = self._get_service(eip_label) - if not eip: - return _format_result('eip: disabled') + @register_method('dict') + def do_GET_SMTP_TOKEN(self, mail, *parts): + d = mail.get_smtp_token() + return d - if subcmd == 'status': - return _format_result(eip.do_status()) + @register_method('dict') + def do_GET_SMTP_CERTIFICATE(self, mail, *parts, **kw): + # TODO move to mail service + # TODO should ask for confirmation? like --force or something, + # if we already have a valid one. or better just refuse if cert + # exists. + # TODO how should we pass the userid?? + # - Keep an 'active' user in bonafide (last authenticated) + # (doing it now) + # - Get active user from Mail Service (maybe preferred?) + # - Have a command/method to set 'active' user. + + @defer.inlineCallbacks + def save_cert(cert_data): + userid, cert_str = cert_data + cert_path = yield mail.do_get_smtp_cert_path(userid) + with open(cert_path, 'w') as outf: + outf.write(cert_str) + defer.returnValue('certificate saved to %s' % cert_path) + + bonafide = kw['bonafide'] + d = bonafide.do_get_smtp_cert() + d.addCallback(save_cert) + return d - elif subcmd == 'disable': - return _format_result( - self.core.do_disable_service(eip_label)) - elif subcmd == 'start': - # TODO --- attempt to get active provider - # TODO or catch the exception and send error - provider = parts[2] - d = eip.do_start(provider) - d.addCallbacks(_format_result, _format_error) - return d +class CommandDispatcher(object): - elif subcmd == 'stop': - d = eip.do_stop() - d.addCallbacks(_format_result, _format_error) - return d + __metaclass__ = APICommand - def do_MAIL(self, *parts): + label = 'core' + def __init__(self, core): + + self.core = core + self.subcommand_user = UserCmd() + self.subcommand_eip = EIPCmd() + self.subcommand_mail = MailCmd() + + # XXX -------------------------------------------- + # TODO move general services to another subclass + + @register_method("{'mem_usage': str}") + def do_STATS(self, *parts): + return _format_result(self.core.do_stats()) + + @register_method("{version_core': '0.0.0'}") + def do_VERSION(self, *parts): + return _format_result(self.core.do_version()) + + @register_method("{'mail': 'running'}") + def do_STATUS(self, *parts): + return _format_result(self.core.do_status()) + + @register_method("{'shutdown': 'ok'}") + def do_SHUTDOWN(self, *parts): + return _format_result(self.core.do_shutdown()) + + # ----------------------------------------------- + + def do_USER(self, *parts): + bonafide = self._get_service('bonafide') + d = self.subcommand_user.dispatch(bonafide, *parts) + d.addCallbacks(_format_result, _format_error) + return d + + def do_EIP(self, *parts): + eip = self._get_service(self.subcommand_eip.label) + if not eip: + return _format_result('eip: disabled') subcmd = parts[1] - mail_label = 'mail' - if subcmd == 'enable': - return _format_result( - self.core.do_enable_service(mail_label)) + dispatch = self._subcommand_eip.dispatch + if subcmd in ('enable', 'disable'): + d = dispatch(self.core, *parts) + else: + d = dispatch(eip, *parts) - m = self._get_service(mail_label) - bf = self._get_service('bonafide') + d.addCallbacks(_format_result, _format_error) + return d - if not m: - return _format_result('mail: disabled') + def do_MAIL(self, *parts): + subcmd = parts[1] + dispatch = self.subcommand_mail.dispatch - if subcmd == 'status': - return _format_result(m.do_status()) + if subcmd == 'enable': + d = dispatch(self.core, *parts) - elif subcmd == 'disable': - return _format_result(self.core.do_disable_service(mail_label)) + mail = self._get_service(self.subcommand_mail.label) + bonafide = self._get_service('bonafide') + kw = {'bonafide': bonafide} - elif subcmd == 'get_imap_token': - d = m.get_imap_token() - d.addCallbacks(_format_result, _format_error) - return d + if not mail: + return _format_result('mail: disabled') - elif subcmd == 'get_smtp_token': - d = m.get_smtp_token() - d.addCallbacks(_format_result, _format_error) - return d + if subcmd == 'disable': + d = dispatch(self.core) + else: + d = dispatch(mail, *parts, **kw) - elif subcmd == 'get_smtp_certificate': - # TODO move to mail service - # TODO should ask for confirmation? like --force or something, - # if we already have a valid one. or better just refuse if cert - # exists. - # TODO how should we pass the userid?? - # - Keep an 'active' user in bonafide (last authenticated) - # (doing it now) - # - Get active user from Mail Service (maybe preferred?) - # - Have a command/method to set 'active' user. - - @defer.inlineCallbacks - def save_cert(cert_data): - userid, cert_str = cert_data - cert_path = yield m.do_get_smtp_cert_path(userid) - with open(cert_path, 'w') as outf: - outf.write(cert_str) - defer.returnValue('certificate saved to %s' % cert_path) - - d = bf.do_get_smtp_cert() - d.addCallback(save_cert) - d.addCallbacks(_format_result, _format_error) - return d + d.addCallbacks(_format_result, _format_error) + return d def do_KEYS(self, *parts): subcmd = parts[1] @@ -187,6 +247,22 @@ class CommandDispatcher(object): d.addCallbacks(_format_result, _format_error) return d + def dispatch(self, msg): + cmd = msg[0] + + _method = getattr(self, 'do_' + cmd.upper(), None) + + if not _method: + return defer.fail(failure.Failure(RuntimeError('No such command'))) + + return defer.maybeDeferred(_method, *msg) + + def _get_service(self, name): + try: + return self.core.getServiceNamed(name) + except KeyError: + return None + def _format_result(result): return json.dumps({'error': None, 'result': result}) diff --git a/src/leap/bitmask/core/dummy.py b/src/leap/bitmask/core/dummy.py new file mode 100644 index 00000000..99dfafa5 --- /dev/null +++ b/src/leap/bitmask/core/dummy.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# dummy.py +# Copyright (C) 2016 LEAP Encryption Acess Project +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +An authoritative dummy backend for tests. +""" +import json + +from leap.common.service_hooks import HookableService + + +class BackendCommands(object): + + """ + General commands for the BitmaskBackend Core Service. + """ + + def __init__(self, core): + self.core = core + + def do_status(self): + return json.dumps( + {'soledad': 'running', + 'keymanager': 'running', + 'mail': 'running', + 'eip': 'stopped', + 'backend': 'dummy'}) + + def do_version(self): + return {'version_core': '0.0.1'} + + def do_stats(self): + return {'mem_usage': '01 KB'} + + def do_shutdown(self): + return {'shutdown': 'ok'} + + +class mail_services(object): + + class SoledadService(HookableService): + pass + + class KeymanagerService(HookableService): + pass + + class StandardMailService(HookableService): + pass + + +class BonafideService(HookableService): + + def __init__(self, basedir): + pass + + def do_authenticate(self, user, password): + return {u'srp_token': u'deadbeef123456789012345678901234567890123', + u'uuid': u'01234567890abcde01234567890abcde'} + + def do_signup(self, user, password): + return {'signup': 'ok', 'user': 'dummyuser@provider.example.org'} + + def do_logout(self, user, password): + return {'logout': 'ok'} + + def do_get_active_user(self): + return 'dummyuser@provider.example.org' diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index 3495aa39..fb9ee698 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -64,10 +64,10 @@ class ImproperlyConfigured(Exception): class SoledadContainer(Container): - def __init__(self, basedir=DEFAULT_BASEDIR): + def __init__(self, service=None, basedir=DEFAULT_BASEDIR): self._basedir = os.path.expanduser(basedir) self._usermap = UserMap() - super(SoledadContainer, self).__init__() + super(SoledadContainer, self).__init__(service=service) def add_instance(self, userid, passphrase, uuid=None, token=None): @@ -89,7 +89,7 @@ class SoledadContainer(Container): uuid, passphrase, soledad_path, soledad_url, cert_path, token) - self.add_instances(userid, soledad) + super(SoledadContainer, self).add_instance(userid, soledad) data = {'user': userid, 'uuid': uuid, 'token': token, 'soledad': soledad} @@ -202,9 +202,9 @@ class SoledadService(HookableService): class KeymanagerContainer(Container): - def __init__(self, basedir): + def __init__(self, service=None, basedir=DEFAULT_BASEDIR): self._basedir = os.path.expanduser(basedir) - super(KeymanagerContainer, self).__init__() + super(KeymanagerContainer, self).__init__(service=service) def add_instance(self, userid, token, uuid, soledad): diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index fca51048..99132c2d 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -25,19 +25,30 @@ from twisted.python import log from leap.bitmask import __version__ from leap.bitmask.core import configurable -from leap.bitmask.core import mail_services from leap.bitmask.core import _zmq from leap.bitmask.core import flags -from leap.bonafide.service import BonafideService from leap.common.events import server as event_server # from leap.vpn import EIPService +backend = flags.BACKEND + +if backend == 'default': + from leap.bitmask.core import mail_services + from leap.bonafide.service import BonafideService +elif backend == 'dummy': + from leap.bitmask.core.dummy import mail_services + from leap.bitmask.core.dummy import BonafideService +else: + raise RuntimeError('Backend not supported') + + class BitmaskBackend(configurable.ConfigurableService): def __init__(self, basedir=configurable.DEFAULT_BASEDIR): configurable.ConfigurableService.__init__(self, basedir) + self.core_commands = BackendCommands(self) def enabled(service): return self.get_config('services', service, False, boolean=True) @@ -117,38 +128,19 @@ class BitmaskBackend(configurable.ConfigurableService): service.setServiceParent(self) return service - # General commands for the BitmaskBackend Core Service - def do_stats(self): - log.msg('BitmaskCore Service STATS') - mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss - return 'BitmaskCore: [Mem usage: %s KB]' % (mem / 1024) + return self.core_commands.do_stats() def do_status(self): - # we may want to make this tuple a class member - services = ('soledad', 'keymanager', 'mail', 'eip') - - status = {} - for name in services: - _status = 'stopped' - try: - if self.getServiceNamed(name).running: - _status = 'running' - except KeyError: - pass - status[name] = _status - status['backend'] = flags.BACKEND - - return json.dumps(status) + return self.core_commands.do_status() def do_version(self): - version = __version__ - return 'BitmaskCore: %s' % version + return self.core_commands.do_version() def do_shutdown(self): - self.stopService() - reactor.callLater(1, reactor.stop) - return 'shutting down...' + return self.core_commands.do_shutdown() + + # Service Toggling def do_enable_service(self, service): assert service in self.service_names @@ -175,3 +167,43 @@ class BitmaskBackend(configurable.ConfigurableService): # TODO -- should stop also? self.set_config('services', service, 'False') return 'ok' + + +class BackendCommands(object): + + """ + General commands for the BitmaskBackend Core Service. + """ + + def __init__(self, core): + self.core = core + + def do_status(self): + # we may want to make this tuple a class member + services = ('soledad', 'keymanager', 'mail', 'eip') + + status = {} + for name in services: + _status = 'stopped' + try: + if self.core.getServiceNamed(name).running: + _status = 'running' + except KeyError: + pass + status[name] = _status + status['backend'] = flags.BACKEND + + return json.dumps(status) + + def do_version(self): + return {'version_core': __version__} + + def do_stats(self): + log.msg('BitmaskCore Service STATS') + mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + return {'mem_usage': '%s KB' % (mem / 1024)} + + def do_shutdown(self): + self.core.stopService() + reactor.callLater(1, reactor.stop) + return {'shutdown': 'ok'} -- cgit v1.2.3 From cfdd93937a7af65be73d5cc482686d1fe559d0a9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 3 May 2016 10:18:06 -0400 Subject: [refactor] rename and move api_contract --- src/leap/bitmask/core/api_contract.py | 40 ----------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 src/leap/bitmask/core/api_contract.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/core/api_contract.py b/src/leap/bitmask/core/api_contract.py deleted file mode 100644 index 86b600c1..00000000 --- a/src/leap/bitmask/core/api_contract.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# api_contract.py -# Copyright (C) 2016 LEAP Encryption Acess Project -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -Display a human-readable representation of the methods that compound the public -api for Bitmask Core. - -The values are meant to be type annotations. -""" - -if __name__ == "__main__": - from leap.bitmask.core.service import BitmaskBackend - from leap.bitmask.core import api - backend = BitmaskBackend() - - print '========= Bitmask Core API ==================' - print - - for key in api.registry: - human_key = key.replace('do_', '').lower() - value = api.registry[key] - - print("{}:\t\t{}".format( - human_key, - ' '.join([x for x in value]))) - print - print '=============================================' -- cgit v1.2.3 From 54560623ac5c325bebbe627582b3895c3354368a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 3 May 2016 12:18:25 -0400 Subject: [feature] enable to download attachments from within webkit - Resolves: #8069 - Releases: 0.9.2 --- src/leap/bitmask/gui/qt_browser.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index c62e7770..2f7e6086 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -17,8 +17,10 @@ """ QtWebKit-based browser to display Pixelated User Agent """ +import os +import urlparse -from PySide import QtCore, QtWebKit, QtGui +from PySide import QtCore, QtWebKit, QtGui, QtNetwork PIXELATED_URI = 'http://localhost:9090' @@ -37,3 +39,29 @@ class PixelatedWindow(QtGui.QDialog): def load_app(self): self.view.load(QtCore.QUrl(PIXELATED_URI)) + self.view.page().setForwardUnsupportedContent(True) + self.view.page().unsupportedContent.connect(self.download) + + self.manager = QtNetwork.QNetworkAccessManager() + self.manager.finished.connect(self.finished) + + def download(self, reply): + self.request = QtNetwork.QNetworkRequest(reply.url()) + self.reply = self.manager.get(self.request) + + def finished(self): + url = self.reply.url().toString() + + filename = urlparse.parse_qs(url).get('filename', None) + if filename: + filename = filename[0] + name = filename or url + + path = os.path.expanduser(os.path.join( + '~', unicode(name).split('/')[-1])) + destination = QtGui.QFileDialog.getSaveFileName(self, "Save", path) + if destination: + filename = destination[0] + with open(filename, 'wb') as f: + f.write(str(self.reply.readAll())) + f.close() -- cgit v1.2.3 From e7439f48b4279dcda0dc966903840a2affee3353 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 14:45:09 -0700 Subject: [bug] add scrollbars to mail preference panel --- src/leap/bitmask/gui/preferences_email_page.py | 20 +- src/leap/bitmask/gui/ui/preferences_email_page.ui | 648 ++++++++++++---------- 2 files changed, 354 insertions(+), 314 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 7186450d..0f44dfee 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -65,20 +65,16 @@ class PreferencesEmailPage(PreferencesPage): "Search for \"Bitmask\" in the add-on manager or " "download it from addons.mozilla.org.".format( thunderbird_extension_url))) + self.ui.mail_client_label.setText(self.tr( "Alternatively, you can manually configure your mail client to " - "use Bitmask Email with these options:")) + "use Bitmask with these options:")) self.ui.webmail_label.setText(self.tr( - "This distribution of Bitmask ships an experimental integration " - "of Pixelated " - "Mail. Note: at the current state, anyone with access to " - "your device can read your mail without authentication, " - "by opening a browser pointing to http://localhost:9090 ")) - webmail_enabled = self.settings.get_pixelmail_enabled() - self.ui.webmail_checkbox.setChecked(webmail_enabled) - if not HAS_PIXELATED: - self.ui.webmail_box.setVisible(False) + "Bitmask Mail is an integrated mail client based " + "on Pixelated " + "User Agent. If enabled, any user on your device " + "can read your mail by opening http://localhost:9090")) self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) @@ -122,6 +118,10 @@ class PreferencesEmailPage(PreferencesPage): self.ui.message_label.setText( self.tr('You must be logged in to edit email settings.')) else: + webmail_enabled = self.settings.get_pixelmail_enabled() + self.ui.webmail_checkbox.setChecked(webmail_enabled) + if not HAS_PIXELATED: + self.ui.webmail_box.setVisible(False) self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui index e4a72951..610a43c7 100644 --- a/src/leap/bitmask/gui/ui/preferences_email_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -32,345 +32,385 @@ + + background: palette(base); + Tab 1 - - - Bitmask Mail + + + - - - - - Enable Bitmask Mail (needs restart) - - - - - - - webmail info - - - true - - - - - - - - - - Thunderbird Configuration + + QFrame::NoFrame - - - - - thunderbird information - - - true - - - - - - - - - - - 0 - 0 - - - - Other Mail Clients Configuration + + true - - - - - mail client information - - - true - - - - - - - - - - - - 0 - 0 - - - - Host - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - + + + + 0 + 0 + 504 + 537 + + + + false + + + background: palette(base); + + + + 6 + + + 0 + + + + + Bitmask Mail + + + + - Port - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Enable Bitmask Mail (needs restart) - - - - - 0 - 0 - - - - - 100 - 16777215 - - + + - + webmail info - + true - - - - - 0 - 0 - - + + + + + + + Thunderbird Configuration + + + + - TLS: off - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - + thunderbird information - + true - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - IMAP - - - - - - - Username - - - - - - - SMTP - - - - - - - - - - 0 - 0 - - + + + + + + + 0 + 0 + + + + Other Mail Clients Configuration + + + + - Host + mail client information - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - - - + true - - - - - 0 - 0 - - - - Port - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - TLS: off - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - true - - + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + IMAP + + + + + + + Username + + + + + + + SMTP + + + + + + + + + + 0 + 0 + + + + Host + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + Port + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + TLS: off + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + true + + + + + + + + + true + + + + + + + Password + + + + + + + true + + + + - - - - - true - - - - - - - Password - - - - - - - true - - - - - - + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + - - - - Qt::Vertical - - - - 20 - 40 - - - - -- cgit v1.2.3 From c7f9bbcc8fe075750e4041c5a7f00c8767dd38b5 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 15:03:23 -0700 Subject: [feat] Make help menu item open bitmask.net/help directly --- src/leap/bitmask/gui/mainwindow.py | 50 +++----------------------------------- 1 file changed, 3 insertions(+), 47 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index daf49eb6..25ee4f3d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1054,53 +1054,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): TRIGGERS: self.ui.action_help.triggered - Display the Bitmask help dialog. - """ - # TODO: don't hardcode! - smtp_port = 2013 - - help_url = "

    {0}

    ".format( - self.tr("bitmask.net/help")) - - lang = QtCore.QLocale.system().name().replace('_', '-') - thunderbird_extension_url = \ - "https://addons.mozilla.org/{0}/" \ - "thunderbird/addon/bitmask/".format(lang) - - email_quick_reference = self.tr("Email quick reference") - thunderbird_text = self.tr( - "For Thunderbird, you can use the " - "Bitmask extension. Search for \"Bitmask\" in the add-on " - "manager or download it from " - "addons.mozilla.org.".format(thunderbird_extension_url)) - manual_text = self.tr( - "Alternatively, you can manually configure " - "your mail client to use Bitmask Email with these options:") - manual_imap = self.tr("IMAP: localhost, port {0}".format(IMAP_PORT)) - manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port)) - manual_username = self.tr("Username: your full email address") - - # FIXME on i3, this doens't allow to mouse-select. - # Switch to a dialog in which we can set the QLabel - mail_auth_token = ( - self.app.service_tokens.get('mail_auth', None) or - "??? (log in to unlock)") - mail_password = self.tr("IMAP/SMTP Password:") + " %s" % ( - mail_auth_token,) - - msg = help_url + self.tr( - "

    {0}

    " - "

    {1}

    " - "

    {2}" - "

      " - "
    •  {3}
    • " - "
    •  {4}
    • " - "
    •  {5}
    • " - "
    •  {6}
    • " - "

    ").format(email_quick_reference, thunderbird_text, - manual_text, manual_imap, manual_smtp, - manual_username, mail_password) - QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) + Open bitmask.net/help + """ + QtGui.QDesktopServices.openUrl("https://bitmask.net/help") def _needs_update(self): """ -- cgit v1.2.3 From cd95c8d2e285538e282dbf2b5e848b4154c3606c Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 15:05:57 -0700 Subject: [bug] remove "about bitmask" easter egg --- src/leap/bitmask/gui/mainwindow.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 25ee4f3d..e7b849e5 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1030,12 +1030,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): Display the About Bitmask dialog """ today = datetime.now().date() - greet = ("Happy New 1984!... or not ;)

    " - if today.month == 1 and today.day < 15 else "") title = self.tr("About Bitmask - %s") % (VERSION,) msg = self.tr( "Version: {ver} ({ver_hash})
    " - "
    {greet}" "Bitmask is the Desktop client application for the LEAP " "platform, supporting Encrypted Internet Proxy " "and " @@ -1046,7 +1043,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): "available.
    " "
    " "
    More about LEAP") - msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10], greet=greet) + msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10]) QtGui.QMessageBox.about(self, title, msg) def _help(self): -- cgit v1.2.3 From 61970d6395afcaa5439d866fbfb1ab2ad471141a Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 May 2016 17:09:54 -0700 Subject: [feat] move pix UA launcher from menu to mail status widget. --- src/leap/bitmask/gui/mail_status.py | 52 +++++-- src/leap/bitmask/gui/mainwindow.py | 12 -- src/leap/bitmask/gui/preferenceswindow.py | 15 ++ src/leap/bitmask/gui/ui/mail_status.ui | 239 +++++++++++++++++------------- src/leap/bitmask/gui/ui/mainwindow.ui | 10 +- src/leap/bitmask/gui/ui/preferences.ui | 2 +- 6 files changed, 197 insertions(+), 133 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 419a85c0..1045343b 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -26,7 +26,9 @@ from leap.common.check import leap_assert, leap_assert_type from leap.common.events import register from leap.common.events import catalog +from leap.bitmask.gui.preferenceswindow import PreferencesWindow from ui_mail_status import Ui_MailStatusWidget +from .qt_browser import PixelatedWindow logger = get_logger() @@ -52,13 +54,21 @@ class MailStatusWidget(QtGui.QWidget): self._systray = None self._disabled = True self._started = False + self._mainwindow = parent self._unread_mails = 0 self.ui = Ui_MailStatusWidget() self.ui.setupUi(self) - self.ui.lblMailReadyHelp.setVisible(False) + self.ui.email_ready.setVisible(False) + self.ui.configure_button.clicked.connect( + self._show_configure) + self.ui.open_mail_button.clicked.connect( + self._show_pix_ua) + if not self._mainwindow._settings.get_pixelmail_enabled(): + self.ui.open_mail_button.setVisible(False) + self.ui.or_label.setVisible(False) # set systray tooltip status self._mx_status = "" @@ -144,7 +154,23 @@ class MailStatusWidget(QtGui.QWidget): self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) - # Systray and actions + # + # Button actions + # + + def _show_configure(self): + pref_win = PreferencesWindow(self._mainwindow, self._mainwindow.app) + pref_win.set_page("email") + pref_win.show() + + def _show_pix_ua(self): + win = PixelatedWindow(self._mainwindow) + win.show() + win.load_app() + + # + # Systray + # def set_systray(self, systray): """ @@ -166,6 +192,10 @@ class MailStatusWidget(QtGui.QWidget): mx_status = u"{0}: {1}".format(self._service_name, self._mx_status) self._systray.set_service_tooltip(MX_SERVICE, mx_status) + # + # Status + # + def set_action_mail_status(self, action_mail_status): """ Sets the action_mail_status to use. @@ -229,6 +259,9 @@ class MailStatusWidget(QtGui.QWidget): elif ready < 0: tray_status = self.tr("Mail is disabled") + if ready < 1: + self._hide_mail_ready() + self.ui.lblMailStatusIcon.setPixmap(icon) self._action_mail_status.setText(tray_status) self._update_systray_tooltip() @@ -424,9 +457,10 @@ class MailStatusWidget(QtGui.QWidget): self._show_unread_mails() elif event == catalog.IMAP_SERVICE_STARTED: self._imap_started = True - elif event == catalog.IMAP_CLIENT_LOGIN: - # If a MUA has logged in then we don't need to show this. - self._hide_mail_ready_help() + # this is disabled for now, because this event was being + # triggered at weird times. + #elif event == catalog.IMAP_CLIENT_LOGIN: + # self._hide_mail_ready() if ext_status is not None: self._set_mail_status(ext_status, ready=1) @@ -495,15 +529,13 @@ class MailStatusWidget(QtGui.QWidget): Display the correct UI for the connected state. """ self._set_mail_status(self.tr("ON"), 2) + self.ui.email_ready.setVisible(True) - # this help message will hide when the MUA connects - self.ui.lblMailReadyHelp.setVisible(True) - - def _hide_mail_ready_help(self): + def _hide_mail_ready(self): """ Hide the mail help message on the UI. """ - self.ui.lblMailReadyHelp.setVisible(False) + self.ui.email_ready.setVisible(False) def mail_state_disabled(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e7b849e5..6637f170 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -59,8 +59,6 @@ from leap.bitmask.util.keyring_helpers import has_keyring from leap.common.events import register from leap.common.events import catalog -from .qt_browser import PixelatedWindow - from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow @@ -227,11 +225,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) - self.ui.action_pixelated_mail.triggered.connect( - self._show_pixelated_browser) - - pixelated_enabled = self._settings.get_pixelmail_enabled() - self.ui.action_pixelated_mail.setVisible(pixelated_enabled) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) @@ -575,11 +568,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): pref_win = PreferencesWindow(self, self.app) pref_win.show() - def _show_pixelated_browser(self): - win = PixelatedWindow(self) - win.show() - win.load_app() - def _update_eip_enabled_status(self, account=None, services=None): """ TRIGGER: diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 82dc8d77..30091312 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -38,6 +38,12 @@ class PreferencesWindow(QtGui.QDialog): _current_window = None # currently visible preferences window + _panels = { + "account": 0, + "vpn": 1, + "email": 2 + } + def __init__(self, parent, app): """ :param parent: parent object of the PreferencesWindow. @@ -240,3 +246,12 @@ class PreferencesWindow(QtGui.QDialog): Triggered by get srp_status_logged_in, srp_status_not_logged_in """ self._set_account(self.app.current_account()) + + def set_page(self, page): + """ + Jump to a particular page + """ + index = PreferencesWindow._panels[page] + self.ui.nav_widget.setCurrentRow(index) + self.ui.pages_widget.setCurrentIndex(index) + diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui index 89e1843f..f8ebb5a8 100644 --- a/src/leap/bitmask/gui/ui/mail_status.ui +++ b/src/leap/bitmask/gui/ui/mail_status.ui @@ -6,12 +6,12 @@ 0 0 - 417 - 185 + 427 + 157
    - + 0 0 @@ -20,26 +20,131 @@ Form - - 0 - + + + + color: rgb(80, 80, 80); + + + You must login to use encrypted email. + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + + + + :/images/black/22/off.png + + + true + + + + + + + + 0 + + + 6 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Configure Client + + + + + + + + 0 + 0 + + + + or + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Open Bitmask Mail + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 10 + 10 + + + + + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + + + + + :/images/black/32/email.png + + + + + + + 0 @@ -51,89 +156,18 @@ - - - + + + + Qt::Horizontal + + - 24 - 24 + 1 + 1 - - - - - :/images/black/22/off.png - - - true - - - - - - - false - - - background-color: #e0efd8; -padding: 10px; -margin-top:5px; - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - Bitmask is ready to encrypt your email. Go to <a href="https://bitmask.net/en/help/email">https://bitmask.net/en/help/email</a> for email application setup instructions. - - - Qt::AutoText - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - 0 - - - -1 - - - true - - - - - - - - - - :/images/black/32/email.png - - - - - - - color: rgb(80, 80, 80); - - - You must login to use encrypted email. - - +
    @@ -141,6 +175,7 @@ margin-top:5px; + diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 0dd0b891..5d8e0f35 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -75,7 +75,7 @@ 0 0 524 - 549 + 541
    @@ -306,7 +306,7 @@ 0 0 524 - 21 + 25 @@ -315,7 +315,6 @@ - @@ -371,11 +370,6 @@ Create a new account... - - - Bitmask Mail - - false diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui index 51cad0a1..8e884a63 100644 --- a/src/leap/bitmask/gui/ui/preferences.ui +++ b/src/leap/bitmask/gui/ui/preferences.ui @@ -7,7 +7,7 @@ 0 0 630 - 500 + 560 -- cgit v1.2.3 From 6d4c24af969d72331f0177ec302b9d48381683b8 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 May 2016 20:49:31 -0400 Subject: [style] pep8 --- src/leap/bitmask/gui/mail_status.py | 2 +- src/leap/bitmask/gui/preferences_email_page.py | 2 +- src/leap/bitmask/gui/preferenceswindow.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 1045343b..cb0314b5 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -459,7 +459,7 @@ class MailStatusWidget(QtGui.QWidget): self._imap_started = True # this is disabled for now, because this event was being # triggered at weird times. - #elif event == catalog.IMAP_CLIENT_LOGIN: + # elif event == catalog.IMAP_CLIENT_LOGIN: # self._hide_mail_ready() if ext_status is not None: diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 0f44dfee..93c77df1 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -121,7 +121,7 @@ class PreferencesEmailPage(PreferencesPage): webmail_enabled = self.settings.get_pixelmail_enabled() self.ui.webmail_checkbox.setChecked(webmail_enabled) if not HAS_PIXELATED: - self.ui.webmail_box.setVisible(False) + self.ui.webmail_box.setVisible(False) self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 30091312..50a972e1 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -39,9 +39,9 @@ class PreferencesWindow(QtGui.QDialog): _current_window = None # currently visible preferences window _panels = { - "account": 0, - "vpn": 1, - "email": 2 + "account": 0, + "vpn": 1, + "email": 2 } def __init__(self, parent, app): @@ -254,4 +254,3 @@ class PreferencesWindow(QtGui.QDialog): index = PreferencesWindow._panels[page] self.ui.nav_widget.setCurrentRow(index) self.ui.pages_widget.setCurrentIndex(index) - -- cgit v1.2.3 From 13b5afd1bdea38e908bb774becfc0f49a532d5bd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 May 2016 13:38:07 -0400 Subject: [feat] resize qt-browser to a sensible default mainly to avoid the "Send" button to become out of view in the Compose pane (it needed scrolling to get it on view). this resize shouldn't be needed when pixelated solves the size-responsiveness issue. --- src/leap/bitmask/gui/qt_browser.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/qt_browser.py b/src/leap/bitmask/gui/qt_browser.py index 2f7e6086..2d9e20e6 100644 --- a/src/leap/bitmask/gui/qt_browser.py +++ b/src/leap/bitmask/gui/qt_browser.py @@ -37,6 +37,11 @@ class PixelatedWindow(QtGui.QDialog): self.setLayout(layout) self.setWindowTitle('Bitmask Mail') + # For the moment, we need to resize to a sensible default to avoid the + # "send" button to be out of view in the compose pane. This should be + # removed as soon as pixelated becomes size-responsive. + self.resize(800, 700) + def load_app(self): self.view.load(QtCore.QUrl(PIXELATED_URI)) self.view.page().setForwardUnsupportedContent(True) -- cgit v1.2.3