summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKali Kaneko <kali@leap.se>2015-01-08 14:34:13 -0400
committerKali Kaneko <kali@leap.se>2015-01-08 14:34:13 -0400
commitfdbfe431c52b2bc5a88a2328fe79de3035201099 (patch)
tree60d6af48f468f0dcd1922998e663f2273a55c41e
parent77b576b58f7f533ff4f6a31594bb53d4ffad9d49 (diff)
parent54521d35d239c2e62d42e9c77690b9d1bc94f7db (diff)
Merge branch 'release/0.8.x' into debian/experimental
-rw-r--r--changes/VERSION_COMPAT1
-rw-r--r--changes/backend-frontend-certificates3
-rw-r--r--changes/bug-6123_forward-right-env-data1
-rw-r--r--changes/bug-6150_better-missing-polkit-msg1
-rw-r--r--changes/bug_6146_handle_removals_release1
-rw-r--r--changes/feature_support-arch-nobody1
-rw-r--r--pkg/linux/bitmask-root30
-rwxr-xr-xpkg/linux/build_bundle.sh116
-rwxr-xr-xpkg/osx/build_bundle.sh123
-rw-r--r--pkg/osx/build_bundle_from_linux.sh84
-rwxr-xr-xpkg/scripts/bitmask_bootstrap.sh76
-rwxr-xr-xpkg/tools/enable_ipdb.sh95
-rwxr-xr-xpkg/tuf/init.py102
-rw-r--r--src/leap/bitmask/app.py31
-rw-r--r--src/leap/bitmask/backend/backend.py2
-rw-r--r--src/leap/bitmask/backend/backend_proxy.py33
-rw-r--r--src/leap/bitmask/backend/components.py9
-rw-r--r--src/leap/bitmask/backend/signaler.py1
-rw-r--r--src/leap/bitmask/backend/utils.py26
-rw-r--r--src/leap/bitmask/backend_app.py16
-rw-r--r--src/leap/bitmask/crypto/srpauth.py10
-rw-r--r--src/leap/bitmask/frontend_app.py2
-rw-r--r--src/leap/bitmask/gui/app.py1
-rw-r--r--src/leap/bitmask/gui/login.py537
-rw-r--r--src/leap/bitmask/gui/mainwindow.py448
-rw-r--r--src/leap/bitmask/gui/signaltracker.py64
-rw-r--r--src/leap/bitmask/gui/ui/login.ui51
-rw-r--r--src/leap/bitmask/gui/wizard.py74
-rw-r--r--src/leap/bitmask/platform_init/initializers.py56
-rw-r--r--src/leap/bitmask/services/mail/conductor.py8
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py3
-rw-r--r--src/leap/bitmask/util/leap_argparse.py7
-rw-r--r--src/leap/bitmask/util/polkit_agent.py46
-rw-r--r--src/leap/bitmask/util/privilege_policies.py7
34 files changed, 1061 insertions, 1005 deletions
diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT
index cc00ecf7..1eadcbe0 100644
--- a/changes/VERSION_COMPAT
+++ b/changes/VERSION_COMPAT
@@ -8,3 +8,4 @@
#
# BEGIN DEPENDENCY LIST -------------------------
# leap.foo.bar>=x.y.z
+leap.keymanager>=0.4.0
diff --git a/changes/backend-frontend-certificates b/changes/backend-frontend-certificates
new file mode 100644
index 00000000..2b3c1990
--- /dev/null
+++ b/changes/backend-frontend-certificates
@@ -0,0 +1,3 @@
+- Allow frontend and backend to be run separately. Closes #5873.
+- Reduce the wait for running threads timeout on quit.
+- Create zmq certificates if they don't exist.
diff --git a/changes/bug-6123_forward-right-env-data b/changes/bug-6123_forward-right-env-data
new file mode 100644
index 00000000..10bd8604
--- /dev/null
+++ b/changes/bug-6123_forward-right-env-data
@@ -0,0 +1 @@
+- Forward the right environment data to subprocess call. Closes #6123.
diff --git a/changes/bug-6150_better-missing-polkit-msg b/changes/bug-6150_better-missing-polkit-msg
new file mode 100644
index 00000000..ee317135
--- /dev/null
+++ b/changes/bug-6150_better-missing-polkit-msg
@@ -0,0 +1 @@
+- Do not allow Bitmask to start if there is no polkit agent running. Closes #6150.
diff --git a/changes/bug_6146_handle_removals_release b/changes/bug_6146_handle_removals_release
new file mode 100644
index 00000000..6fe67d62
--- /dev/null
+++ b/changes/bug_6146_handle_removals_release
@@ -0,0 +1 @@
+- Make pkg/tuf/release.py handle removals in the repo
diff --git a/changes/feature_support-arch-nobody b/changes/feature_support-arch-nobody
new file mode 100644
index 00000000..6aa587a3
--- /dev/null
+++ b/changes/feature_support-arch-nobody
@@ -0,0 +1 @@
+- Support 'nobody' (used on Arch) as well as 'nogroup' as group names. Related to #6058.
diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root
index 622a0b8a..6fb1f0b3 100644
--- a/pkg/linux/bitmask-root
+++ b/pkg/linux/bitmask-root
@@ -51,7 +51,29 @@ cmdcheck = subprocess.check_output
# CONSTANTS
#
-VERSION = "4"
+
+def get_no_group_name():
+ """
+ Return the right group name to use for the current OS.
+ Examples:
+ - Ubuntu: nogroup
+ - Arch: nobody
+
+ :rtype: str or None
+ """
+ import grp
+ try:
+ grp.getgrnam('nobody')
+ return 'nobody'
+ except KeyError:
+ try:
+ grp.getgrnam('nogroup')
+ return 'nogroup'
+ except KeyError:
+ return None
+
+
+VERSION = "5"
SCRIPT = "bitmask-root"
NAMESERVER = "10.42.0.1"
BITMASK_CHAIN = "bitmask"
@@ -68,7 +90,7 @@ IPTABLES = "/sbin/iptables"
IP6TABLES = "/sbin/ip6tables"
OPENVPN_USER = "nobody"
-OPENVPN_GROUP = "nogroup"
+OPENVPN_GROUP = get_no_group_name()
LEAPOPENVPN = "LEAPOPENVPN"
OPENVPN_SYSTEM_BIN = "/usr/sbin/openvpn" # Debian location
OPENVPN_LEAP_BIN = "/usr/local/sbin/leap-openvpn" # installed by bundle
@@ -83,10 +105,12 @@ FIXED_FLAGS = [
"--management-signal",
"--script-security", "1",
"--user", "nobody",
- "--group", "nogroup",
"--remap-usr1", "SIGTERM",
]
+if OPENVPN_GROUP is not None:
+ FIXED_FLAGS.extend(["--group", OPENVPN_GROUP])
+
ALLOWED_FLAGS = {
"--remote": ["IP", "NUMBER", "PROTO"],
"--tls-cipher": ["CIPHER"],
diff --git a/pkg/linux/build_bundle.sh b/pkg/linux/build_bundle.sh
deleted file mode 100755
index 60151a80..00000000
--- a/pkg/linux/build_bundle.sh
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/bin/bash
-#
-# USAGE NOTES:
-#
-# This script is meant to be used as follows:
-# user@host ~ $ ./build_bundle.sh ~/tmp 0.3.2 ~/tmp/0.3.1/Bitmask-linux64-0.3.1/ /media/Shared/CHANGELOG ~/tmp/bundle_out/
-#
-# So we would have:
-# REPOS_ROOT -> ~/tmp
-# VERSION -> 0.3.2
-# TEMPLATE_BUNDLE -> ~/tmp/0.3.1/Bitmask-linux64-0.3.1/
-# JOINT_CHANGELOG -> /media/Shared/CHANGELOG
-# DEST -> ~/tmp/bundle_out/
-#
-# We need to set different PATHs in order to use a specific version of PySide,
-# supposing that we have our compiled pyside in '~/pyside/sandbox', the above command would be:
-# user@host ~ $ PYTHONPATH=~/pyside/sandbox/lib/python2.7/site-packages/ LD_LIBRARY_PATH=~/pyside/sandbox/lib/ PATH=$PATH:~/pyside/sandbox/bin/ ./build_bundle.sh ~/tmp 0.3.2 ~/tmp/0.3.1/Bitmask-linux64-0.3.1/ /media/sf_Shared/CHANGELOG ~/tmp/bundle_out/
-
-
-# Required arguments
-REPOS_ROOT=$1 # Root path for all the needed repositories
-VERSION=$2 # Version number that we are building
-TEMPLATE_BUNDLE=$3 # A template used to create the new bundle
-JOINT_CHANGELOG=$4 # Joint changelog for all the repositories
-DEST=$5 # Destination folder for the bundle
-
-# Helper variables
-REPOSITORIES="bitmask_client leap_pycommon soledad keymanager leap_mail"
-ARCH=$(uname -m | sed 's/x86_//;s/i[3-6]86/32/')
-
-# Bundle structure
-LEAP_LIB=$TEMPLATE_BUNDLE/lib/leap/
-BITMASK_BIN=$TEMPLATE_BUNDLE/bitmask
-BUNDLE_NAME=Bitmask-linux$ARCH-$VERSION
-
-# clean template
-rm -f $TEMPLATE_BUNDLE/CHANGELOG
-rm -f $TEMPLATE_BUNDLE/relnotes.txt
-rm -rf $TEMPLATE_BUNDLE/apps/leap
-rm -rf $TEMPLATE_BUNDLE/lib/leap/{common,keymanager,soledad,mail}
-
-# checkout the latest tag in all repos
-for repo in $REPOSITORIES; do
- cd $REPOS_ROOT/$repo
- git checkout master
- git pull --ff-only origin master && git fetch
- git reset --hard origin/master # this avoids problems if you are in a commit far in the past
- # checkout to the closest annotated tag, supress 'detached head' warning
- git checkout --quiet `git describe --abbrev=0`
-done
-
-# make: compile ui and resources in client
-cd $REPOS_ROOT/bitmask_client
-make
-
-# copy the latest client code to the template
-cp -r $REPOS_ROOT/bitmask_client/src/leap $TEMPLATE_BUNDLE/apps/leap
-
-# setup sdist client
-cd $REPOS_ROOT/bitmask_client
-python setup.py sdist
-
-# extract $VERSION and copy _version.py to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/_version.py
-# copy _version.py (versioneer) and reqs.txt (requirements) to the bundle template
-cd dist
-rm -rf leap.bitmask-$VERSION
-tar xzf leap.bitmask-$VERSION.tar.gz
-cp leap.bitmask-$VERSION/src/leap/bitmask/_version.py $TEMPLATE_BUNDLE/apps/leap/bitmask/_version.py
-cp leap.bitmask-$VERSION/src/leap/bitmask/util/reqs.txt $TEMPLATE_BUNDLE/apps/leap/bitmask/util/reqs.txt
-
-# add the other needed projects to $LEAP_LIB
-# e.g. TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/lib/leap/
-cp -r $REPOS_ROOT/leap_pycommon/src/leap/common $LEAP_LIB
-cp -r $REPOS_ROOT/soledad/common/src/leap/soledad $LEAP_LIB
-cp -r $REPOS_ROOT/soledad/client/src/leap/soledad/client $LEAP_LIB/soledad
-cp -r $REPOS_ROOT/leap_mail/src/leap/mail $LEAP_LIB
-cp -r $REPOS_ROOT/keymanager/src/leap/keymanager $LEAP_LIB
-
-# copy bitmask launcher to the bundle template
-# e.g. TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/Bitmask
-cd $REPOS_ROOT/bitmask_launcher/build/
-make
-cp src/launcher $BITMASK_BIN
-
-# copy launcher.py to template bundle
-# e.g. TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/
-cd $REPOS_ROOT/bitmask_launcher/src/
-cp launcher.py $TEMPLATE_BUNDLE/apps/
-
-# copy relnotes, joint changelog and LICENSE to TEMPLATE_BUNDLE
-cp $REPOS_ROOT/bitmask_client/relnotes.txt $TEMPLATE_BUNDLE
-cp $JOINT_CHANGELOG $TEMPLATE_BUNDLE/CHANGELOG
-cp $REPOS_ROOT/bitmask_client/LICENSE $TEMPLATE_BUNDLE/LICENSE
-
-# clean *.pyc files
-cd $TEMPLATE_BUNDLE
-find . -name "*.pyc" -delete
-
-# remove execution flags (because vbox fs) and set read permissions for all
-chmod 644 CHANGELOG LICENSE README
-
-# create tarball
-TMP=/tmp/$BUNDLE_NAME
-
-rm -rf $TMP && mkdir -p $TMP # clean temp dir
-cp -R $TEMPLATE_BUNDLE/* $TMP
-cd /tmp
-tar cjf $DEST/$BUNDLE_NAME.tar.bz2 $BUNDLE_NAME
-cd
-rm -rf $TMP
-
-# go back to develop in all repos
-for repo in $REPOSITORIES; do
- cd $REPOS_ROOT/$repo
- git checkout develop
-done
diff --git a/pkg/osx/build_bundle.sh b/pkg/osx/build_bundle.sh
deleted file mode 100755
index a13746bf..00000000
--- a/pkg/osx/build_bundle.sh
+++ /dev/null
@@ -1,123 +0,0 @@
-REPOS_ROOT=$1
-VERSION=$2
-TEMPLATE_BUNDLE=$3
-JOINT_CHANGELOG=$4
-DEST=$5
-
-# clean template
-
-rm $TEMPLATE_BUNDLE/CHANGELOG.txt
-rm $TEMPLATE_BUNDLE/relnotes.txt
-rm -rf $TEMPLATE_BUNDLE/Bitmask.app/Contentes/MacOS/apps/leap
-rm $TEMPLATE_BUNDLE/Bitmask.app/Contentes/MacOS/lib/leap/{common,keymanager,soledad,mail}
-
-# checkout VERSION in all repos
-
-for i in {leap_client,leap_pycommon,soledad,keymanager,leap_mail}
- do
- cd $REPOS_ROOT/$i
- git checkout $VERSION
- done
-
-# make ui in client
-
-cd $REPOS_ROOT/leap_client
-make
-
-# cp client
-
-cp -r $REPOS_ROOT/leap_client/src/leap $TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap
-
-# setup sdist client
-
-cd $REPOS_ROOT/leap_client
-python setup.py sdist
-
-# extract $VERSION and copy _version.py to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/_version.py
-
-cd dist
-rm -rf leap.bitmask-$VERSION
-tar xzf leap.bitmask-$VERSION.tar.gz
-cp leap.bitmask-$VERSION/src/leap/bitmask/_version.py $TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/_version.py
-cp leap.bitmask-$VERSION/src/leap/bitmask/util/reqs.txt $TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/util/reqs.txt
-
-# cp common, soledad(client and common), mail and keymanager in TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/lib/leap/
-
-LEAP_LIB=$TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/lib/leap/
-
-cp -r $REPOS_ROOT/leap_pycommon/src/leap/common $LEAP_LIB
-cp -r $REPOS_ROOT/soledad/common/src/leap/soledad $LEAP_LIB
-cp -r $REPOS_ROOT/soledad/client/src/leap/soledad/client $LEAP_LIB/soledad
-cp -r $REPOS_ROOT/leap_mail/src/leap/mail $LEAP_LIB
-cp -r $REPOS_ROOT/keymanager/src/leap/keymanager $LEAP_LIB
-
-# cp leap_client launcher to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/Bitmask
-
-BITMASK_BIN=$TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/Bitmask
-
-cd $REPOS_ROOT/leap_client_launcher/build/
-make
-cp src/launcher $BITMASK_BIN
-
-# cp launcher.py to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/
-
-cd $REPOS_ROOT/leap_client_launcher/src/
-cp launcher.py $TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/
-
-# install_name_tool it
-
-install_name_tool -change libboost_python.dylib lib/libboost_python.dylib $BITMASK_BIN
-install_name_tool -change libboost_filesystem.dylib lib/libboost_filesystem.dylib $BITMASK_BIN
-install_name_tool -change libboost_system.dylib lib/libboost_system.dylib $BITMASK_BIN
-
-# cp relnotes to TEMPLATE_BUNDLE
-
-cp $REPOS_ROOT/leap_client/relnotes.txt $TEMPLATE_BUNDLE
-
-# cp joint_chglog to TEMPLATE_BUNDLE
-
-cp $JOINT_CHANGELOG $TEMPLATE_BUNDLE/CHANGELOG.txt
-
-# cp LICENSE to TEMPLATE_BUNDLE
-
-cp $REPOS_ROOT/leap_client/LICENSE $TEMPLATE_BUNDLE/LICENSE.txt
-
-# clean pyc$
-
-cd $TEMPLATE_BUNDLE
-for i in $(find . | grep pyc$);
- do
- rm $i
- done
-
-# create dmg
-
-TMP=/tmp/Bitmask
-VOLUME_NAME=Bitmask
-DMG_FILE=Bitmask-OSX-$VERSION.dmg
-
-rm -rf $TMP
-mkdir -p $TMP
-cp -R $TEMPLATE_BUNDLE/* $TMP
-cp $REPOS_ROOT/leap_assets/mac/bitmask.icns $TMP/.VolumeIcon.icns
-SetFile -c icnC $TMP/.VolumeIcon.icns
-hdiutil create -srcfolder $TMP -volname $VOLUME_NAME -format UDRW -ov $DEST/raw-$DMG_FILE
-
-rm -rf $TMP
-mkdir -p $TMP
-hdiutil attach $DEST/raw-$DMG_FILE -mountpoint $TMP
-
-SetFile -a C $TMP
-hdiutil detach $TMP
-
-rm -rf $TMP
-rm -f $DEST/$DMG_FILE
-hdiutil convert $DEST/raw-$DMG_FILE -format UDZO -o $DEST/$DMG_FILE
-rm -f $DEST/raw-$DMG_FILE
-
-# go back to develop in all repos
-for i in {leap_client,leap_pycommon,soledad,keymanager,leap_mail}
- do
- cd $REPOS_ROOT/$i
- git checkout develop
- done
diff --git a/pkg/osx/build_bundle_from_linux.sh b/pkg/osx/build_bundle_from_linux.sh
deleted file mode 100644
index c98e1b7a..00000000
--- a/pkg/osx/build_bundle_from_linux.sh
+++ /dev/null
@@ -1,84 +0,0 @@
-REPOS_ROOT=$1
-VERSION=$2
-TEMPLATE_BUNDLE=$3
-JOINT_CHANGELOG=$4
-DEST=$5
-
-# clean template
-
-rm $TEMPLATE_BUNDLE/CHANGELOG.txt
-rm $TEMPLATE_BUNDLE/relnotes.txt
-rm -rf $TEMPLATE_BUNDLE/Bitmask.app/Contentes/MacOS/apps/leap
-rm $TEMPLATE_BUNDLE/Bitmask.app/Contentes/MacOS/lib/leap/{common,keymanager,soledad,mail}
-
-# checkout VERSION in all repos
-
-for i in {leap_client,leap_pycommon,soledad,keymanager,leap_mail}
- do
- cd $REPOS_ROOT/$i
- git checkout $VERSION
- done
-
-# make ui in client
-
-cd $REPOS_ROOT/leap_client
-make
-
-# cp client
-
-cp -r $REPOS_ROOT/leap_client/src/leap $TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap
-
-# setup sdist client
-
-cd $REPOS_ROOT/leap_client
-python setup.py sdist
-
-# extract $VERSION and copy _version.py to TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/_version.py
-
-cd dist
-rm -rf leap.bitmask-$VERSION
-tar xzf leap.bitmask-$VERSION.tar.gz
-cp leap.bitmask-$VERSION/src/leap/bitmask/_version.py $TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/_version.py
-cp leap.bitmask-$VERSION/src/leap/bitmask/util/reqs.txt $TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/leap/bitmask/util/reqs.txt
-
-# cp common, soledad(client and common), mail and keymanager in TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/lib/leap/
-
-LEAP_LIB=$TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/lib/leap/
-
-cp -r $REPOS_ROOT/leap_pycommon/src/leap/common $LEAP_LIB
-cp -r $REPOS_ROOT/soledad/common/src/leap/soledad $LEAP_LIB
-cp -r $REPOS_ROOT/soledad/client/src/leap/soledad/client $LEAP_LIB/soledad
-cp -r $REPOS_ROOT/leap_mail/src/leap/mail $LEAP_LIB
-cp -r $REPOS_ROOT/keymanager/src/leap/keymanager $LEAP_LIB
-
-# cp relnotes to TEMPLATE_BUNDLE
-
-cp $REPOS_ROOT/leap_client/relnotes.txt $TEMPLATE_BUNDLE
-
-# cp joint_chglog to TEMPLATE_BUNDLE
-
-cp $JOINT_CHANGELOG $TEMPLATE_BUNDLE/CHANGELOG.txt
-
-# cp LICENSE to TEMPLATE_BUNDLE
-
-cp $REPOS_ROOT/leap_client/LICENSE $TEMPLATE_BUNDLE/LICENSE.txt
-
-# clean pyc$
-
-cd $TEMPLATE_BUNDLE
-for i in $(find . | grep pyc$);
- do
- rm $i
- done
-
-# create dmg
-
-genisoimage -D -V "Bitmask" -no-pad -r -apple -o raw-Bitmask-OSX-$VERSION.dmg $TEMPLATE_BUNDLE
-dmg dmg raw-Bitmask-OSX-$VERSION.dmg Bitmask-OSX-$VERSION.dmg
-
-# go back to develop in all repos
-for i in {leap_client,leap_pycommon,soledad,keymanager,leap_mail}
- do
- cd $REPOS_ROOT/$i
- git checkout develop
- done
diff --git a/pkg/scripts/bitmask_bootstrap.sh b/pkg/scripts/bitmask_bootstrap.sh
deleted file mode 100755
index 70f9867e..00000000
--- a/pkg/scripts/bitmask_bootstrap.sh
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/bin/bash
-######################################################################
-# bitmask_boostrap.sh
-# Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
-######################################################################
-# Installs requirements, and
-# clones the latest leap-client
-
-# depends on:
-# (authoritative list under docs/dev/quickstart.rst)
-
-# git python-dev python-setuptools python-virtualenv python-pip python-openssl libsqlite3-dev g++ openvpn
-# pyside-tools python-pyside python-qt4
-
-# Clone latest git (develop branch)
-# change "develop" for any other branch you want.
-BRANCH="develop"
-BITMASK_DIR="bitmask-develop"
-
-# Escape code
-esc=`echo -en "\033"`
-
-# Set colors
-cc_green="${esc}[0;32m"
-cc_yellow="${esc}[0;33m"
-cc_blue="${esc}[0;34m"
-cc_red="${esc}[0;31m"
-cc_normal=`echo -en "${esc}[m\017"`
-
-echo "${cc_yellow}"
-echo "~~~~~~~~~~~~~~~~~~~~~~~"
-echo " Bitmask bootstrapping "
-echo "~~~~~~~~~~~~~~~~~~~~~~~"
-echo ""
-echo "${cc_green}Creating virtualenv...${cc_normal}"
-
-mkdir ${BITMASK_DIR}
-virtualenv "${BITMASK_DIR}"
-source ./${BITMASK_DIR}/bin/activate
-
-echo "${cc_green}Installing bitmask...${cc_normal}"
-
-pip install -e 'git+https://leap.se/git/bitmask_client@'${BRANCH}'#egg=leap.bitmask'
-
-cd ${BITMASK_DIR}
-
-# symlink the pyside libraries to the system libs
-./src/leap.bitmask/pkg/postmkvenv.sh
-
-cd ./src/leap.bitmask
-make
-cd ../../
-source ./bin/activate
-
-echo "${cc_green}bitmask installed! =)"
-echo "${cc_yellow}"
-echo "Launch it with: "
-echo "~~~~~~~~~~~~~~~~~~~~~~"
-echo "bin/bitmask --debug"
-echo "~~~~~~~~~~~~~~~~~~~~~~"
-echo "If you are not inside the virtualenv, source it first with "
-echo "source "${BITMASK_DIR}"/bin/activate"
-echo "${cc_normal}"
diff --git a/pkg/tools/enable_ipdb.sh b/pkg/tools/enable_ipdb.sh
new file mode 100755
index 00000000..00a9235b
--- /dev/null
+++ b/pkg/tools/enable_ipdb.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+# enable_ipdb.sh
+# This script installs modules needed for using IPython debug shell in a
+# Bitmask bundle directory. It uses a python virtual environment in which it
+# installs needed modules and then links them into the appropriate directory
+# inside the bundle directory.
+
+# Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
+
+
+MODULES="ast.py runpy.py"
+SITE_MODULES="ipdb IPython simplegeneric.py decorator.py pexpect"
+
+if [ $# != 1 ]; then
+ echo "Usage: $0 bundle_path"
+ exit 1
+fi
+
+BUNDLE_PATH=`echo $1 | sed -e "s/\/\$//"`
+BUNDLE_LIB=${BUNDLE_PATH}/lib
+BUNDLE_VENV=${BUNDLE_PATH}/.venv
+
+function check_bundle_dirs() {
+ if [ ! -d ${BUNDLE_PATH} ]; then
+ echo "Argument ${BUNDLE_PATH} is not a directory."
+ exit 2
+ fi
+
+ if [ ! -d ${BUNDLE_LIB} ]; then
+ echo "Expected library directory ${BUNDLE_LIB} is not a directory."
+ exit 2
+ fi
+
+ if [ ! -w ${BUNDLE_LIB} ]; then
+ echo "Directory ${BUNDLE_LIB} is not writable."
+ exit 2
+ fi
+}
+
+function confirm_installation() {
+ echo -n "Are you sure you want to enable IPython debugger in ${BUNDLE_PATH} (y/N)? "
+ read confirm
+ if [[ "${confirm}" != "y" && "${confirm}" != "Y" ]]; then
+ echo "Bailing out..."
+ exit 0
+ fi
+}
+
+function setup_virtualenv() {
+ if [ ! -d ${BUNDLE_VENV} ]; then
+ virtualenv ${BUNDLE_VENV}
+ fi
+ source ${BUNDLE_VENV}/bin/activate
+ pip install ipdb
+}
+
+function link_modules() {
+ for package in ${MODULES}; do
+ package_path=${BUNDLE_LIB}/${package}
+ if [[ ! -f ${package_path} && ! -d ${package_path} ]]; then
+ ln -sf /usr/lib/python2.7/${package} ${BUNDLE_LIB}
+ fi
+ done
+ for package in ${SITE_MODULES}; do
+ package_path=${BUNDLE_LIB}/${package}
+ if [[ ! -f ${package_path} && ! -d ${package_path} ]]; then
+ ln -sf ${BUNDLE_VENV}/lib/python2.7/site-packages/${package} ${BUNDLE_LIB}
+ fi
+ done
+}
+
+function main() {
+ check_bundle_dirs
+ confirm_installation
+ setup_virtualenv
+ link_modules
+ echo "All done."
+ exit 0
+}
+
+main
diff --git a/pkg/tuf/init.py b/pkg/tuf/init.py
deleted file mode 100755
index 7300da0a..00000000
--- a/pkg/tuf/init.py
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/usr/bin/env python
-# init.py
-# 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 <http://www.gnu.org/licenses/>.
-
-"""
-Tool to initialize a TUF repo.
-
-The keys can be generated with:
- openssl genrsa -des3 -out private.pem 4096
-The public key can be exported with:
- openssl rsa -in private.pem -outform PEM -pubout -out public.pem
-"""
-
-import sys
-
-from tuf.repository_tool import create_new_repository
-from tuf.repository_tool import import_rsa_privatekey_from_file
-from tuf.repository_tool import import_rsa_publickey_from_file
-
-
-def usage():
- print ("Usage: %s repo root_private_key root_pub_key targets_pub_key"
- " timestamp_pub_key") % (sys.argv[0],)
-
-
-def main():
- if len(sys.argv) < 6:
- usage()
- return
-
- repo_path = sys.argv[1]
- root_priv_path = sys.argv[2]
- root_pub_path = sys.argv[3]
- targets_pub_path = sys.argv[4]
- timestamp_pub_path = sys.argv[5]
- repo = Repo(repo_path, root_priv_path)
- repo.build(root_pub_path, targets_pub_path, timestamp_pub_path)
-
- print "%s/metadata.staged/root.json is ready" % (repo_path,)
-
-
-class Repo(object):
- """
- Repository builder class
- """
-
- def __init__(self, repo_path, key_path):
- """
- Constructor
-
- :param repo_path: path where the repo lives
- :type repo_path: str
- :param key_path: path where the private root key lives
- :type key_path: str
- """
- self._repo_path = repo_path
- self._key = import_rsa_privatekey_from_file(key_path)
-
- def build(self, root_pub_path, targets_pub_path, timestamp_pub_path):
- """
- Create a new repo
-
- :param root_pub_path: path where the public root key lives
- :type root_pub_path: str
- :param targets_pub_path: path where the public targets key lives
- :type targets_pub_path: str
- :param timestamp_pub_path: path where the public timestamp key lives
- :type timestamp_pub_path: str
- """
- repository = create_new_repository(self._repo_path)
-
- pub_root_key = import_rsa_publickey_from_file(root_pub_path)
- repository.root.add_verification_key(pub_root_key)
- repository.root.load_signing_key(self._key)
-
- pub_target_key = import_rsa_publickey_from_file(targets_pub_path)
- repository.targets.add_verification_key(pub_target_key)
- repository.snapshot.add_verification_key(pub_target_key)
- repository.targets.compressions = ["gz"]
- repository.snapshot.compressions = ["gz"]
-
- pub_timestamp_key = import_rsa_publickey_from_file(timestamp_pub_path)
- repository.timestamp.add_verification_key(pub_timestamp_key)
-
- repository.write_partial()
-
-
-if __name__ == "__main__":
- main()
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index ad886bc4..9056d2a6 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -44,7 +44,7 @@ import os
import sys
-from leap.bitmask.backend.utils import generate_certificates
+from leap.bitmask.backend.backend_proxy import BackendProxy
from leap.bitmask import __version__ as VERSION
from leap.bitmask.config import flags
@@ -127,7 +127,9 @@ def start_app():
}
flags.STANDALONE = opts.standalone
- flags.OFFLINE = opts.offline
+ # XXX Disabled right now since it's not tested after login refactor
+ # flags.OFFLINE = opts.offline
+ flags.OFFLINE = False
flags.MAIL_LOGFILE = opts.mail_log_file
flags.APP_VERSION_CHECK = opts.app_version_check
flags.API_VERSION_CHECK = opts.api_version_check
@@ -176,19 +178,24 @@ def start_app():
logger.info('Starting app')
- generate_certificates()
+ backend_running = BackendProxy().check_online()
+ logger.debug("Backend online: {0}".format(backend_running))
flags_dict = flags_to_dict()
- frontend_pid = os.getpid()
- backend = lambda: run_backend(opts.danger, flags_dict, frontend_pid)
- backend_process = multiprocessing.Process(target=backend, name='Backend')
- # we don't set the 'daemon mode' since we need to start child processes in
- # the backend
- # backend_process.daemon = True
- backend_process.start()
-
- run_frontend(options, flags_dict, backend_pid=backend_process.pid)
+ 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')
+ # we don't set the 'daemon mode' since we need to start child processes
+ # in the backend
+ # backend_process.daemon = True
+ backend_process.start()
+ backend_pid = backend_process.pid
+
+ run_frontend(options, flags_dict, backend_pid=backend_pid)
if __name__ == "__main__":
diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py
index 37535f37..75eff8a9 100644
--- a/src/leap/bitmask/backend/backend.py
+++ b/src/leap/bitmask/backend/backend.py
@@ -135,7 +135,7 @@ class Backend(object):
i.e.:
use threads.deferToThread(this_method) instead of this_method()
"""
- wait_max = 5 # seconds
+ wait_max = 3 # seconds
wait_step = 0.5
wait = 0
while self._ongoing_defers and wait < wait_max:
diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py
index e2611251..3e79289f 100644
--- a/src/leap/bitmask/backend/backend_proxy.py
+++ b/src/leap/bitmask/backend/backend_proxy.py
@@ -28,6 +28,7 @@ import time
import zmq
from leap.bitmask.backend.api import API, STOP_REQUEST, PING_REQUEST
+from leap.bitmask.backend.utils import generate_zmq_certificates_if_needed
from leap.bitmask.backend.utils import get_backend_certificates
import logging
@@ -49,6 +50,8 @@ class BackendProxy(object):
PING_INTERVAL = 2 # secs
def __init__(self):
+ generate_zmq_certificates_if_needed()
+
self._socket = None
# initialize ZMQ stuff:
@@ -67,6 +70,7 @@ class BackendProxy(object):
socket.curve_serverkey = public
socket.setsockopt(zmq.RCVTIMEO, 1000)
+ socket.setsockopt(zmq.LINGER, 0) # Terminate early
socket.connect(self.SERVER)
self._socket = socket
@@ -75,8 +79,23 @@ class BackendProxy(object):
self._call_queue = Queue.Queue()
self._worker_caller = threading.Thread(target=self._worker)
+
+ def start(self):
self._worker_caller.start()
+ def check_online(self):
+ """
+ Return whether the backend is accessible or not.
+ You don't need to do `run` in order to use this.
+
+ :rtype: bool
+ """
+ # we use a small timeout in order to response quickly if the backend is
+ # offline
+ self._send_request(PING_REQUEST, retry=False, timeout=500)
+ self._socket.close()
+ return self.online
+
def _worker(self):
"""
Worker loop that processes the Queue of pending requests to do.
@@ -150,7 +169,7 @@ class BackendProxy(object):
if api_method == STOP_REQUEST:
self._call_queue.put(STOP_REQUEST)
- def _send_request(self, request):
+ def _send_request(self, request, retry=True, timeout=None):
"""
Send the given request to the server.
This is used from a thread safe loop in order to avoid sending a
@@ -158,6 +177,10 @@ class BackendProxy(object):
:param request: the request to send.
:type request: str
+ :param retry: whether we should retry or not in case of timeout.
+ :type retry: bool
+ :param timeout: a custom timeout (milliseconds) to wait for a response.
+ :type timeout: int
"""
# logger.debug("Sending request to backend: {0}".format(request))
self._socket.send(request)
@@ -166,10 +189,16 @@ class BackendProxy(object):
poll.register(self._socket, zmq.POLLIN)
reply = None
+
tries = 0
+ if not retry:
+ tries = self.POLL_TRIES + 1 # this means: no retries left
+
+ if timeout is None:
+ timeout = self.POLL_TIMEOUT
while True:
- socks = dict(poll.poll(self.POLL_TIMEOUT))
+ socks = dict(poll.poll(timeout))
if socks.get(self._socket) == zmq.POLLIN:
reply = self._socket.recv()
break
diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py
index 5ef6befd..4b63af84 100644
--- a/src/leap/bitmask/backend/components.py
+++ b/src/leap/bitmask/backend/components.py
@@ -54,6 +54,7 @@ from leap.bitmask.services.mail.smtpconfig import SMTPConfig
from leap.bitmask.services.soledad.soledadbootstrapper import \
SoledadBootstrapper
from leap.bitmask.util import force_eval
+from leap.bitmask.util.privilege_policies import LinuxPolicyChecker
from leap.common import certs as leap_certs
@@ -638,6 +639,10 @@ class EIP(object):
:param domain: the domain for the provider to check
:type domain: str
"""
+ if not LinuxPolicyChecker.is_up():
+ logger.error("No polkit agent running.")
+ return False
+
eip_config = eipconfig.EIPConfig()
provider_config = ProviderConfig.get_provider_config(domain)
@@ -914,6 +919,8 @@ class Keymanager(object):
keymanager = self._keymanager_proxy
try:
+ # NOTE: parse_openpgp_ascii_key is not in keymanager anymore
+ # the API for that will need some thinking
public_key, private_key = keymanager.parse_openpgp_ascii_key(
new_key)
except (KeyAddressMismatch, KeyFingerprintMismatch) as e:
@@ -974,7 +981,7 @@ class Keymanager(object):
"""
List all the keys stored in the local DB.
"""
- keys = self._keymanager_proxy.get_all_keys_in_local_db()
+ keys = self._keymanager_proxy.get_all_keys()
self._signaler.signal(self._signaler.keymanager_keys_list, keys)
def get_key_details(self, username):
diff --git a/src/leap/bitmask/backend/signaler.py b/src/leap/bitmask/backend/signaler.py
index 574bfa71..43cba994 100644
--- a/src/leap/bitmask/backend/signaler.py
+++ b/src/leap/bitmask/backend/signaler.py
@@ -60,6 +60,7 @@ class Signaler(object):
socket.curve_serverkey = public
socket.setsockopt(zmq.RCVTIMEO, 1000)
+ socket.setsockopt(zmq.LINGER, 0) # Terminate early
socket.connect(self.SERVER)
self._socket = socket
diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py
index 65bf6753..18e70743 100644
--- a/src/leap/bitmask/backend/utils.py
+++ b/src/leap/bitmask/backend/utils.py
@@ -17,6 +17,7 @@
"""
Backend utilities to handle ZMQ certificates.
"""
+import logging
import os
import shutil
import stat
@@ -26,10 +27,12 @@ import zmq.auth
from leap.bitmask.util import get_path_prefix
from leap.common.files import mkdir_p
+logger = logging.getLogger(__name__)
+
KEYS_DIR = os.path.join(get_path_prefix(), 'leap', 'zmq_certificates')
-def generate_certificates():
+def generate_zmq_certificates():
"""
Generate client and server CURVE certificate files.
"""
@@ -62,3 +65,24 @@ def get_backend_certificates(base_dir='.'):
backend_secret_file = os.path.join(KEYS_DIR, "backend.key_secret")
public, secret = zmq.auth.load_certificate(backend_secret_file)
return public, secret
+
+
+def _certificates_exist():
+ """
+ Return whether there are certificates in place or not.
+
+ :rtype: bool
+ """
+ frontend_secret_file = os.path.join(KEYS_DIR, "frontend.key_secret")
+ backend_secret_file = os.path.join(KEYS_DIR, "backend.key_secret")
+ return os.path.isfile(frontend_secret_file) and \
+ os.path.isfile(backend_secret_file)
+
+
+def generate_zmq_certificates_if_needed():
+ """
+ Generate the needed ZMQ certificates for backend/frontend communication if
+ needed.
+ """
+ if not _certificates_exist():
+ generate_zmq_certificates()
diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py
index 716ae4a7..286b04f7 100644
--- a/src/leap/bitmask/backend_app.py
+++ b/src/leap/bitmask/backend_app.py
@@ -22,6 +22,8 @@ import multiprocessing
import signal
from leap.bitmask.backend.leapbackend import LeapBackend
+from leap.bitmask.backend.utils import generate_zmq_certificates
+from leap.bitmask.logs.utils import create_logger
from leap.bitmask.util import dict_to_flags
logger = logging.getLogger(__name__)
@@ -44,7 +46,7 @@ def signal_handler(signum, frame):
logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum))
-def run_backend(bypass_checks, flags_dict, frontend_pid=None):
+def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None):
"""
Run the backend for the application.
@@ -53,12 +55,22 @@ def run_backend(bypass_checks, flags_dict, frontend_pid=None):
:param flags_dict: a dict containing the flag values set on app start.
:type flags_dict: dict
"""
+ # The backend is the one who always creates the certificates. Either if it
+ # is run separately or in a process in the same app as the frontend.
+ generate_zmq_certificates()
+
# ignore SIGINT since app.py takes care of signaling SIGTERM to us.
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal_handler)
- dict_to_flags(flags_dict)
+ if flags_dict is not None:
+ dict_to_flags(flags_dict)
backend = LeapBackend(bypass_checks=bypass_checks,
frontend_pid=frontend_pid)
backend.run()
+
+
+if __name__ == '__main__':
+ logger = create_logger(debug=True)
+ run_backend()
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index d59b3c31..c2a5f158 100644
--- a/src/leap/bitmask/crypto/srpauth.py
+++ b/src/leap/bitmask/crypto/srpauth.py
@@ -561,6 +561,14 @@ class SRPAuth(object):
self._reset_session()
+ # FIXME ---------------------------------------------------------
+ # 1. it makes no sense to defer each callback to a thread
+ # 2. the decision to use threads should be at another level.
+ # (although it's not really needed, that was a hack around
+ # the gui blocks)
+ # it makes very hard to test this. The __impl could be
+ # separated and decoupled from the provider_config abstraction.
+
d = threads.deferToThread(self._authentication_preprocessing,
username=username,
password=password)
@@ -736,6 +744,8 @@ class SRPAuth(object):
:type username: str
:param password: password for this user
:type password: str
+ :returns: a Deferred that will fire when the authentication is done
+ :rtype: Deferred
"""
username = username.lower()
d = self.__instance.authenticate(username, password)
diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py
index 909005f0..b0a149f9 100644
--- a/src/leap/bitmask/frontend_app.py
+++ b/src/leap/bitmask/frontend_app.py
@@ -54,7 +54,7 @@ def signal_handler(window, pid, signum, frame):
window.quit()
-def run_frontend(options, flags_dict, backend_pid):
+def run_frontend(options, flags_dict, backend_pid=None):
"""
Run the GUI for the application.
diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py
index eb1a58d5..75dc4a38 100644
--- a/src/leap/bitmask/gui/app.py
+++ b/src/leap/bitmask/gui/app.py
@@ -41,6 +41,7 @@ class App(QtGui.QWidget):
self.settings = LeapSettings()
self.backend = BackendProxy()
+ self.backend.start()
self.signaler = LeapSignaler()
self.signaler.start()
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index 2a79fafd..7487e888 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -16,13 +16,31 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Login widget implementation
+
+The login sequence is the following:
+ - _do_login
+ - backend.provider_setup (check_name_resolution, check_https, download_provider_info)
+ - on error: _provider_setup_intermediate
+ - on success: _load_provider_config
+ - backend.provider_bootstrap (download_ca_cert, check_ca_fingerprint, check_api_certificate)
+ - on error: _provider_setup_intermediate
+ - on success: _provider_config_loaded
+ - backend.user_login
+ - on error: _authentication_error
+ - on success: _authentication_finished
+
"""
import logging
from PySide import QtCore, QtGui
from ui_login import Ui_LoginWidget
+# TODO: we should use a more granular signaling instead of passing error/ok as
+# a result.
+from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
from leap.bitmask.config import flags
+from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.gui.signaltracker import SignalTracker
from leap.bitmask.util import make_address
from leap.bitmask.util.credentials import USERNAME_REGEX
from leap.bitmask.util.keyring_helpers import has_keyring
@@ -32,59 +50,71 @@ from leap.common.check import leap_assert_type
logger = logging.getLogger(__name__)
-class LoginWidget(QtGui.QWidget):
+class LoginState(object):
+ """
+ This class holds the states related to the login sequence.
+ """
+
+ def __init__(self):
+ # `wait_to_login` defines whether we should wait to start the login
+ # sequence or we should start right away.
+ # This is used, for instance, to hold on until EIP is started since the
+ # firewall could block the login attempt.
+ self.wait_to_login = False
+
+ # This state indicates that the login sequence was required to start
+ # but it was set on hold since we `wait_to_login` was True
+ self.login_waiting = False
+
+ # Full username of the logged user, with the format: 'user@provider'
+ self.full_logged_username = None
+
+
+class LoginWidget(QtGui.QWidget, SignalTracker):
"""
Login widget that emits signals to display the wizard or to
perform login.
"""
- # Emitted when the login button is clicked
- login = QtCore.Signal()
- logged_in_signal = QtCore.Signal()
- cancel_login = QtCore.Signal()
- logout = QtCore.Signal()
+ login_start = QtCore.Signal()
+ login_finished = QtCore.Signal()
+ login_offline_finished = QtCore.Signal()
+ login_failed = QtCore.Signal()
+ logged_out = QtCore.Signal()
MAX_STATUS_WIDTH = 40
# Keyring
KEYRING_KEY = "bitmask"
- def __init__(self, settings, parent=None):
+ def __init__(self, backend, signaler, parent=None):
"""
Constructs the LoginWidget.
- :param settings: client wide settings
- :type settings: LeapSettings
+ :param backend: Backend being used
+ :type backend: Backend
+ :param signaler: Object in charge of handling communication
+ back to the frontend
+ :type signaler: Signaler
:param parent: The parent widget for this widget
:type parent: QWidget or None
"""
QtGui.QWidget.__init__(self, parent)
-
- self._settings = settings
+ SignalTracker.__init__(self)
self.ui = Ui_LoginWidget()
self.ui.setupUi(self)
- self.ui.chkRemember.stateChanged.connect(
- self._remember_state_changed)
+ self.ui.chkRemember.stateChanged.connect(self._remember_state_changed)
self.ui.chkRemember.setEnabled(has_keyring())
- self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password)
-
- self.ui.btnLogin.clicked.connect(self.login)
- self.ui.lnPassword.returnPressed.connect(self.login)
+ self.ui.lnUser.textChanged.connect(self._credentials_changed)
+ self.ui.lnPassword.textChanged.connect(self._credentials_changed)
- self.ui.lnUser.returnPressed.connect(self._focus_password)
+ self.ui.btnLogin.clicked.connect(self._do_login)
+ self.ui.btnLogout.clicked.connect(self.do_logout)
- self.ui.btnLogout.clicked.connect(
- self.logout)
-
- username_re = QtCore.QRegExp(USERNAME_REGEX)
self.ui.lnUser.setValidator(
- QtGui.QRegExpValidator(username_re, self))
-
- self.logged_out()
-
- self.ui.btnLogout.clicked.connect(self.start_logout)
+ QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX), self))
self.ui.clblErrorMsg.hide()
self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide)
@@ -92,20 +122,73 @@ class LoginWidget(QtGui.QWidget):
self.ui.lnUser.textEdited.connect(self.ui.clblErrorMsg.hide)
self.ui.lnPassword.textEdited.connect(self.ui.clblErrorMsg.hide)
+ self._settings = LeapSettings()
+ self._backend = backend
+ self._leap_signaler = signaler
+
+ # the selected provider that we'll use to login
+ self._provider = None
+
+ self._state = LoginState()
+
+ self._set_logged_out()
+
+ @QtCore.Slot(int)
def _remember_state_changed(self, state):
"""
- Saves the remember state in the LeapSettings
+ Save the remember state in the LeapSettings.
+
+ :param state: the current state of the check box.
+ :type state: int
+ """
+ # The possible state values of the checkbox (from QtCore.Qt.CheckState)
+ # are: Checked, Unchecked and PartiallyChecked
+ self._settings.set_remember(state == QtCore.Qt.Checked)
+
+ @QtCore.Slot(unicode)
+ def _credentials_changed(self, text):
+ """
+ TRIGGER:
+ self.ui.lnUser.textChanged
+ self.ui.lnPassword.textChanged
- :param state: possible stats can be Checked, Unchecked and
- PartiallyChecked
- :type state: QtCore.Qt.CheckState
+ Update the 'enabled' status of the login button depending if we have
+ all the fields needed set.
"""
- enable = True if state == QtCore.Qt.Checked else False
- self._settings.set_remember(enable)
+ enabled = self._provider and self.get_user() and self.get_password()
+ enabled = bool(enabled) # provider can be None
+
+ self.ui.btnLogin.setEnabled(enabled)
+
+ def wait_for_login(self, wait):
+ """
+ Set the wait flag to True/False so the next time that a login action is
+ requested it will wait or not.
+
+ If we set the wait to True and we have paused a login request before,
+ this will trigger a login action.
+
+ :param wait: whether we should wait or not on the next login request.
+ :type wait: bool
+ """
+ self._state.wait_to_login = wait
+
+ if not wait and self._state.login_waiting:
+ logger.debug("No more waiting, triggering login sequence.")
+ self._do_login()
+
+ def set_provider(self, provider):
+ """
+ Set the provider to use in the login sequence.
+
+ :param provider: the provider to use.
+ :type provider: unicode
+ """
+ self._provider = provider
def set_remember(self, value):
"""
- Checks the remember user and password checkbox
+ Check the remember user and password checkbox
:param value: True to mark it checked, False otherwise
:type value: bool
@@ -132,12 +215,21 @@ class LoginWidget(QtGui.QWidget):
def get_user(self):
"""
- Returns the user that appears in the widget.
+ Return the user that appears in the widget.
:rtype: unicode
"""
return self.ui.lnUser.text()
+ def get_logged_user(self):
+ """
+ Return the current logged user or None if no user is logged in.
+ The return value has the format: 'user@provider'
+
+ :rtype: unicode or None
+ """
+ return self._state.full_logged_username
+
def set_password(self, password):
"""
Sets the password for the widget
@@ -200,12 +292,12 @@ class LoginWidget(QtGui.QWidget):
:type enabled: bool
"""
text = self.tr("Cancel")
- login_or_cancel = self.cancel_login
+ login_or_cancel = self._do_cancel
hide_remember = enabled
if not enabled:
text = self.tr("Log In")
- login_or_cancel = self.login
+ login_or_cancel = self._do_login
self.ui.btnLogin.setText(text)
@@ -220,40 +312,49 @@ class LoginWidget(QtGui.QWidget):
"""
self.ui.lnPassword.setFocus()
- def start_login(self, provider):
+ def _check_login(self):
"""
- Setups the login widgets for actually performing the login and
- performs some basic checks.
+ Check that we have the needed fields to do the actual login: provider,
+ username and password.
- :param provider: the domain of the current provider
- :type provider: unicode str
- :returns: True if everything's good to go, False otherwise
+ :return: True if everything's good to go, False otherwise.
:rtype: bool
"""
+ provider = self._provider
username = self.get_user()
password = self.get_password()
- self._enabled_services = self._settings.get_enabled_services(provider)
-
- if len(provider) == 0:
- self.set_status(
- self.tr("Please select a valid provider"))
+ if not provider:
+ self.set_status(self.tr("Please select a valid provider"))
return False
- if len(username) == 0:
- self.set_status(
- self.tr("Please provide a valid username"))
+ if not username:
+ self.set_status(self.tr("Please provide a valid username"))
return False
- if len(password) == 0:
- self.set_status(
- self.tr("Please provide a valid password"))
+ if not password:
+ self.set_status(self.tr("Please provide a valid password"))
return False
+ return True
+
+ def _set_logging_in(self):
+ """
+ Set the status of the widget to "Logging in".
+ """
self.set_status(self.tr("Logging in..."), error=False)
self.set_enabled(False)
self.ui.clblErrorMsg.hide()
+ def _save_credentials(self):
+ """
+ If the user asked to remember the credentials, we save them into the
+ keyring.
+ """
+ provider = self._provider
+ username = self.get_user()
+ password = self.get_password()
+
self._settings.set_provider(provider)
if self.get_remember() and has_keyring():
# in the keyring and in the settings
@@ -262,36 +363,209 @@ class LoginWidget(QtGui.QWidget):
try:
keyring = get_keyring()
keyring.set_password(self.KEYRING_KEY,
- full_user_id,
- password.encode("utf8"))
+ full_user_id, password.encode("utf8"))
# Only save the username if it was saved correctly in
# the keyring
self._settings.set_user(full_user_id)
except Exception as e:
- logger.exception("Problem saving data to keyring. %r"
- % (e,))
- return True
+ logger.exception("Problem saving data to keyring. %r" % (e,))
+
+ def do_login(self):
+ """
+ Start the login sequence.
+ We check that we have the needed fields to do the actual login:
+ provider, username and password.
+ If everything is ok we perform the login.
+
+ Note that the actual login won't be started if you set the
+ `wait_to_login` flag, it will be scheduled to get started when you set
+ that flag to False.
+
+ :return: True if the login sequence started, False otherwise.
+ :rtype: bool
+ """
+ ok = self._provider and self.get_user() and self.get_password()
+
+ if ok:
+ self._do_login()
+
+ return bool(ok)
+
+ def _do_login(self):
+ """
+ Start the login sequence.
+ """
+ if self._state.wait_to_login:
+ logger.debug("Login delayed, waiting...")
+
+ self._state.login_waiting = True
+ self.ui.btnLogin.setEnabled(False)
+ self.ui.btnLogin.setText(self.tr("Waiting..."))
+ # explicitly process events to display the button's text change.
+ QtCore.QCoreApplication.processEvents(0, 10)
+
+ return
+ else:
+ self._state.login_waiting = False
+ self.ui.btnLogin.setEnabled(True)
+
+ self.login_start.emit()
+
+ provider = self._provider
+ if flags.OFFLINE:
+ self._do_offline_login()
+ return
+
+ # connect to the backend signals, remember to disconnect after login.
+ self._backend_connect()
+
+ if self._check_login():
+ self._set_logging_in()
+ self._save_credentials()
+ self._backend.provider_setup(provider=provider)
+
+ def _do_cancel(self):
+ logger.debug("Cancelling log in.")
- def logged_in(self, provider):
+ self._backend.provider_cancel_setup()
+ self._backend.user_cancel_login()
+ self._set_logged_out()
+
+ @QtCore.Slot()
+ def _set_login_cancelled(self):
+ """
+ TRIGGERS:
+ Signaler.prov_cancelled_setup
+
+ Re-enable the login widget and display a message for the cancelled
+ operation.
+ """
+ self.set_status(self.tr("Log in cancelled by the user."))
+ self.set_enabled(True)
+
+ @QtCore.Slot(dict)
+ def _provider_setup_intermediate(self, data):
+ """
+ TRIGGERS:
+ self._backend.signaler.prov_name_resolution
+ self._backend.signaler.prov_https_connection
+
+ Handle a possible problem during the provider setup process.
+ If there was a problem, display it, otherwise it does nothing.
+ """
+ if not data[PASSED_KEY]:
+ logger.error(data[ERROR_KEY])
+ self._login_problem_provider()
+
+ @QtCore.Slot()
+ def _login_problem_provider(self):
+ """
+ Warn the user about a problem with the provider during login.
+ """
+ self.set_status(self.tr("Unable to login: Problem with provider"))
+ self.set_enabled(True)
+
+ @QtCore.Slot(dict)
+ def _load_provider_config(self, data):
+ """
+ TRIGGERS:
+ self._backend.signaler.prov_download_provider_info
+
+ Once the provider config has been downloaded, start the second
+ part of the bootstrapping sequence.
+
+ :param data: result from the last stage of the
+ backend.provider_setup()
+ :type data: dict
"""
- Sets the widgets to the logged in state
+ if not data[PASSED_KEY]:
+ logger.error(data[ERROR_KEY])
+ self._login_problem_provider()
+ return
- :param provider: the domain of the current provider
- :type provider: unicode str
+ self._backend.provider_bootstrap(provider=self._provider)
+
+ @QtCore.Slot(dict)
+ def _provider_config_loaded(self, data):
+ """
+ TRIGGERS:
+ self._backend.signaler.prov_check_api_certificate
+
+ Once the provider configuration is loaded, this starts the SRP
+ authentication
"""
+ if not data[PASSED_KEY]:
+ logger.error(data[ERROR_KEY])
+ self._login_problem_provider()
+ return
+
+ self._backend.user_login(provider=self._provider,
+ username=self.get_user(),
+ password=self.get_password())
+
+ # TODO check this!
+ def _do_offline_login(self):
+ logger.debug("OFFLINE mode! bypassing remote login")
+ # TODO reminder, we're not handling logout for offline mode.
+ self._set_logged_in()
+ self._logged_in_offline = True
+ self._set_label_offline()
+ self.login_offline_finished.emit()
+
+ def _set_label_offline(self):
+ """
+ Set the login label to reflect offline status.
+ """
+ # TODO: figure out what widget to use for this. Maybe the window title?
+
+ def _set_logged_in(self):
+ """
+ Set the widgets to the logged in state.
+ """
+ fullname = make_address(self.get_user(), self._provider)
+ self._state.full_logged_username = fullname
self.ui.login_widget.hide()
self.ui.logged_widget.show()
- self.ui.lblUser.setText(make_address(self.get_user(), provider))
+ self.ui.lblUser.setText(fullname)
- if flags.OFFLINE is False:
- self.logged_in_signal.emit()
+ @QtCore.Slot()
+ def _authentication_finished(self):
+ """
+ TRIGGERS:
+ self._srp_auth.authentication_finished
- def logged_out(self):
+ The SRP auth was successful, set the login status.
"""
- Sets the widgets to the logged out state
+ self.set_status(self.tr("Succeeded"), error=False)
+ self._set_logged_in()
+
+ if not flags.OFFLINE:
+ self.login_finished.emit()
+
+ @QtCore.Slot(unicode)
+ def _authentication_error(self, msg):
"""
- # TODO consider "logging out offline" too...
- # how that would be ???
+ TRIGGERS:
+ Signaler.srp_auth_error
+ Signaler.srp_auth_server_error
+ Signaler.srp_auth_connection_error
+ Signaler.srp_auth_bad_user_or_password
+
+ Handle the authentication errors.
+
+ :param msg: the message to show to the user.
+ :type msg: unicode
+ """
+ self.set_status(msg)
+ self.set_enabled(True)
+ self.login_failed.emit()
+
+ def _set_logged_out(self):
+ """
+ Set the widgets to the logged out state.
+ """
+ # TODO consider "logging out offline" too... how that would be ???
+ self._state.full_logged_username = None
self.ui.login_widget.show()
self.ui.logged_widget.hide()
@@ -300,30 +574,75 @@ class LoginWidget(QtGui.QWidget):
self.set_enabled(True)
self.set_status("", error=False)
- def start_logout(self):
+ @QtCore.Slot()
+ def do_logout(self):
"""
- Sets the widgets to the logging out state
+ TRIGGER:
+ self.ui.btnLogout.clicked
+
+ Start the logout sequence and set the widgets to the "logging out"
+ state.
"""
- self.ui.btnLogout.setText(self.tr("Logging out..."))
- self.ui.btnLogout.setEnabled(False)
+ if self._state.full_logged_username is not None:
+ self._set_logging_out()
+ self._backend.user_logout()
+ else:
+ logger.debug("Not logged in.")
- def done_logout(self):
+ def _set_logging_out(self, logging_out=True):
"""
- Sets the widgets to the logged out state
+ Set the status of the logout button.
+
+ logging_out == True:
+ button text -> "Logging out..."
+ button enabled -> False
+
+ logging_out == False:
+ button text -> "Logout
+ button enabled -> True
+
+ :param logging_out: wether we are logging out or not.
+ :type logging_out: bool
"""
- self.ui.btnLogout.setText(self.tr("Logout"))
- self.ui.btnLogout.setEnabled(True)
- self.ui.clblErrorMsg.hide()
+ if logging_out:
+ self.ui.btnLogout.setText(self.tr("Logging out..."))
+ self.ui.btnLogout.setEnabled(False)
+ else:
+ self.ui.btnLogout.setText(self.tr("Logout"))
+ self.ui.btnLogout.setEnabled(True)
+ self.ui.clblErrorMsg.hide()
+
+ @QtCore.Slot()
+ def _logout_error(self):
+ """
+ TRIGGER:
+ self._srp_auth.logout_error
+
+ Inform the user about a logout error.
+ """
+ self._set_logging_out(False)
+ self.set_status(self.tr("Something went wrong with the logout."))
+
+ @QtCore.Slot()
+ def _logout_ok(self):
+ """
+ TRIGGER:
+ self._srp_auth.logout_ok
+
+ Switch the stackedWidget back to the login stage after logging out.
+ """
+ self._set_logging_out(False)
+ self._set_logged_out()
+ self.logged_out.emit()
def load_user_from_keyring(self, saved_user):
"""
- Tries to load a user from the keyring, returns True if it was
- loaded successfully, False otherwise.
+ Try to load a user from the keyring.
- :param saved_user: String containing the saved username as
- user@domain
+ :param saved_user: the saved username as user@domain
:type saved_user: unicode
+ :return: True if the user was loaded successfully, False otherwise.
:rtype: bool
"""
leap_assert_type(saved_user, unicode)
@@ -336,15 +655,13 @@ class LoginWidget(QtGui.QWidget):
return False
self.set_user(username)
-
self.set_remember(True)
saved_password = None
try:
keyring = get_keyring()
- saved_password = keyring.get_password(self.KEYRING_KEY,
- saved_user
- .encode("utf8"))
+ u_user = saved_user.encode("utf8")
+ saved_password = keyring.get_password(self.KEYRING_KEY, u_user)
except ValueError as e:
logger.debug("Incorrect Password. %r." % (e,))
@@ -353,3 +670,49 @@ class LoginWidget(QtGui.QWidget):
return True
return False
+
+ def _backend_connect(self):
+ """
+ Connect to backend signals.
+
+ We track the signals in order to disconnect them on demand.
+ """
+ sig = self._leap_signaler
+ conntrack = self.connect_and_track
+ auth_err = self._authentication_error
+
+ # provider_setup signals
+ conntrack(sig.prov_name_resolution, self._provider_setup_intermediate)
+ conntrack(sig.prov_https_connection, self._provider_setup_intermediate)
+ conntrack(sig.prov_download_provider_info, self._load_provider_config)
+
+ # provider_bootstrap signals
+ conntrack(sig.prov_download_ca_cert, self._provider_setup_intermediate)
+ # XXX missing check_ca_fingerprint connection
+ conntrack(sig.prov_check_api_certificate, self._provider_config_loaded)
+
+ conntrack(sig.prov_problem_with_provider, self._login_problem_provider)
+ conntrack(sig.prov_cancelled_setup, self._set_login_cancelled)
+
+ # Login signals
+ conntrack(sig.srp_auth_ok, self._authentication_finished)
+
+ auth_error = lambda: auth_err(self.tr("Unknown error."))
+ conntrack(sig.srp_auth_error, auth_error)
+
+ auth_server_error = lambda: auth_err(self.tr(
+ "There was a server problem with authentication."))
+ conntrack(sig.srp_auth_server_error, auth_server_error)
+
+ auth_connection_error = lambda: auth_err(self.tr(
+ "Could not establish a connection."))
+ conntrack(sig.srp_auth_connection_error, auth_connection_error)
+
+ auth_bad_user_or_password = lambda: auth_err(self.tr(
+ "Invalid username or password."))
+ conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password)
+
+ # Logout signals
+ sig.srp_logout_ok.connect(self._logout_ok)
+ sig.srp_logout_error.connect(self._logout_error)
+ # sig.srp_not_logged_in_error.connect(self._not_logged_in_error)
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 9df1804f..9201a39a 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -41,6 +41,7 @@ from leap.bitmask.gui.loggerwindow import LoggerWindow
from leap.bitmask.gui.login import LoginWidget
from leap.bitmask.gui.mail_status import MailStatusWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
+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
@@ -72,17 +73,15 @@ QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
-class MainWindow(QtGui.QMainWindow):
+class MainWindow(QtGui.QMainWindow, SignalTracker):
"""
Main window for login and presenting status updates to the user
"""
# Signals
eip_needs_login = QtCore.Signal([])
- offline_mode_bypass_login = QtCore.Signal([])
new_updates = QtCore.Signal(object)
raise_window = QtCore.Signal([])
soledad_ready = QtCore.Signal([])
- logout = QtCore.Signal([])
all_services_stopped = QtCore.Signal()
# We use this flag to detect abnormal terminations
@@ -103,6 +102,8 @@ class MainWindow(QtGui.QMainWindow):
:type start_hidden: bool
"""
QtGui.QMainWindow.__init__(self)
+ SignalTracker.__init__(self)
+
autostart.set_autostart(True)
# register leap events ########################################
@@ -127,7 +128,8 @@ class MainWindow(QtGui.QMainWindow):
self._settings = self.app.settings
# Login Widget
- self._login_widget = LoginWidget(self._settings, self)
+ self._login_widget = LoginWidget(self._backend,
+ self._leap_signaler, self)
self.ui.loginLayout.addWidget(self._login_widget)
# Mail Widget
@@ -142,9 +144,10 @@ class MainWindow(QtGui.QMainWindow):
self.app.service_selection_changed.connect(
self._update_eip_enabled_status)
- self._login_widget.login.connect(self._login)
- self._login_widget.cancel_login.connect(self._cancel_login)
- self._login_widget.logout.connect(self._logout)
+
+ self._login_widget.login_finished.connect(
+ self._on_user_logged_in)
+ self._login_widget.logged_out.connect(self._on_user_logged_out)
self._providers.connect_provider_changed(self._on_provider_changed)
@@ -164,6 +167,8 @@ class MainWindow(QtGui.QMainWindow):
self._eip_conductor.add_eip_widget(self._eip_status)
self._eip_conductor.connect_signals()
+ self._eip_conductor.qtsigs.connecting_signal.connect(
+ self._on_eip_connecting)
self._eip_conductor.qtsigs.connected_signal.connect(
self._on_eip_connection_connected)
self._eip_conductor.qtsigs.disconnected_signal.connect(
@@ -171,7 +176,7 @@ class MainWindow(QtGui.QMainWindow):
self._eip_conductor.qtsigs.connected_signal.connect(
self._maybe_run_soledad_setup_checks)
- self.offline_mode_bypass_login.connect(
+ self._login_widget.login_offline_finished.connect(
self._maybe_run_soledad_setup_checks)
self.eip_needs_login.connect(self._eip_status.disable_eip_start)
@@ -184,8 +189,6 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_started = False
# This is created once we have a valid provider config
- self._srp_auth = None
- self._logged_user = None
self._logged_in_offline = False
# Set used to track the services being stopped and need wait.
@@ -199,7 +202,6 @@ class MainWindow(QtGui.QMainWindow):
# Used to differentiate between a real quit and a close to tray
self._close_to_tray = True
- self._backend_connected_signals = []
self._backend_connect()
self.ui.action_preferences.triggered.connect(self._show_preferences)
@@ -254,8 +256,9 @@ class MainWindow(QtGui.QMainWindow):
self.ui.btnMore.resize(0, 0)
#########################################
self.ui.btnMore.clicked.connect(self._updates_details)
- if flags.OFFLINE is True:
- self._set_label_offline()
+
+ if flags.OFFLINE:
+ self._login_widget._set_label_offline()
# Services signals/slots connection
self.new_updates.connect(self._react_to_new_updates)
@@ -263,8 +266,6 @@ class MainWindow(QtGui.QMainWindow):
self.soledad_ready.connect(self._start_mail_service)
# ################################ end Qt Signals connection ########
- init_platform()
-
self._wizard = None
self._wizard_firstrun = False
@@ -274,7 +275,9 @@ class MainWindow(QtGui.QMainWindow):
self._mail_conductor = mail_conductor.MailConductor(self._backend)
self._mail_conductor.connect_mail_signals(self._mail_status)
- self.logout.connect(self._mail_conductor.stop_mail_services)
+ if not init_platform():
+ self.quit()
+ return
# start event machines from within the eip and mail conductors
@@ -285,7 +288,11 @@ class MainWindow(QtGui.QMainWindow):
if self._first_run():
self._wizard_firstrun = True
- self._disconnect_and_untrack()
+
+ # HACK FIX: disconnection of signals triggers a reconnection later
+ # chich segfaults on wizard quit
+ # self.disconnect_and_untrack()
+
self._wizard = Wizard(backend=self._backend,
leap_signaler=self._leap_signaler)
# Give this window time to finish init and then show the wizard
@@ -309,18 +316,6 @@ class MainWindow(QtGui.QMainWindow):
self.tr("You are trying to do an operation "
"that requires logging in first."))
- def _connect_and_track(self, signal, method):
- """
- Helper to connect signals and keep track of them.
-
- :param signal: the signal to connect to.
- :type signal: QtCore.Signal
- :param method: the method to call when the signal is triggered.
- :type method: callable, Slot or Signal
- """
- self._backend_connected_signals.append((signal, method))
- signal.connect(method)
-
def _backend_bad_call(self, data):
"""
Callback for debugging bad backend calls
@@ -338,7 +333,7 @@ class MainWindow(QtGui.QMainWindow):
We track some signals in order to disconnect them on demand.
For instance, in the wizard we need to connect to some signals that are
already connected in the mainwindow, so to avoid conflicts we do:
- - disconnect signals needed in wizard (`_disconnect_and_untrack`)
+ - disconnect signals needed in wizard (`disconnect_and_untrack`)
- use wizard
- reconnect disconnected signals (we use the `only_tracked` param)
@@ -347,39 +342,14 @@ class MainWindow(QtGui.QMainWindow):
:type only_tracked: bool
"""
sig = self._leap_signaler
- conntrack = self._connect_and_track
- auth_err = self._authentication_error
-
- conntrack(sig.prov_name_resolution, self._intermediate_stage)
- conntrack(sig.prov_https_connection, self._intermediate_stage)
- conntrack(sig.prov_download_ca_cert, self._intermediate_stage)
- conntrack(sig.prov_download_provider_info, self._load_provider_config)
- conntrack(sig.prov_check_api_certificate, self._provider_config_loaded)
- conntrack(sig.prov_check_api_certificate, self._get_provider_details)
-
- conntrack(sig.prov_problem_with_provider, self._login_problem_provider)
- conntrack(sig.prov_cancelled_setup, self._set_login_cancelled)
+ conntrack = self.connect_and_track
+ # XXX does this goes in here? this will be triggered when the login or
+ # wizard requests provider data
+ # XXX - here segfaults if we did a disconnect_and_untrack
+ conntrack(sig.prov_check_api_certificate, self._get_provider_details)
conntrack(sig.prov_get_details, self._provider_get_details)
- # Login signals
- conntrack(sig.srp_auth_ok, self._authentication_finished)
-
- auth_error = lambda: auth_err(self.tr("Unknown error."))
- conntrack(sig.srp_auth_error, auth_error)
-
- auth_server_error = lambda: auth_err(self.tr(
- "There was a server problem with authentication."))
- conntrack(sig.srp_auth_server_error, auth_server_error)
-
- auth_connection_error = lambda: auth_err(self.tr(
- "Could not establish a connection."))
- conntrack(sig.srp_auth_connection_error, auth_connection_error)
-
- auth_bad_user_or_password = lambda: auth_err(self.tr(
- "Invalid username or password."))
- conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password)
-
# EIP bootstrap signals
conntrack(sig.eip_config_ready, self._eip_intermediate_stage)
conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap)
@@ -399,8 +369,9 @@ class MainWindow(QtGui.QMainWindow):
sig.prov_get_all_services.connect(self._provider_get_all_services)
# Logout signals =================================================
- sig.srp_logout_ok.connect(self._logout_ok)
- sig.srp_logout_error.connect(self._logout_error)
+ # This error may be due a 'logout' or a 'password change', as is used
+ # on the login widget and the settings dialog the connection is made
+ # here.
sig.srp_not_logged_in_error.connect(self._not_logged_in_error)
# EIP start signals ==============================================
@@ -431,21 +402,6 @@ class MainWindow(QtGui.QMainWindow):
# TODO: connect this with something
# sig.soledad_cancelled_bootstrap.connect()
- def _disconnect_and_untrack(self):
- """
- Helper to disconnect the tracked signals.
-
- Some signals are emitted from the wizard, and we want to
- ignore those.
- """
- for signal, method in self._backend_connected_signals:
- try:
- signal.disconnect(method)
- except RuntimeError:
- pass # Signal was not connected
-
- self._backend_connected_signals = []
-
@QtCore.Slot()
def _rejected_wizard(self):
"""
@@ -467,7 +423,11 @@ class MainWindow(QtGui.QMainWindow):
# This happens if the user finishes the provider
# setup but does not register
self._wizard = None
- self._backend_connect(only_tracked=True)
+
+ # HACK FIX: disconnection of signals triggers a reconnection later
+ # chich segfaults on wizard quit
+ # self._backend_connect(only_tracked=True)
+
if self._wizard_firstrun:
self._finish_init()
@@ -483,7 +443,9 @@ class MainWindow(QtGui.QMainWindow):
there.
"""
if self._wizard is None:
- self._disconnect_and_untrack()
+ # HACK FIX: disconnection of signals triggers a reconnection later
+ # chich segfaults on wizard quit
+ # self.disconnect_and_untrack()
self._wizard = Wizard(backend=self._backend,
leap_signaler=self._leap_signaler)
self._wizard.accepted.connect(self._finish_init)
@@ -548,8 +510,7 @@ class MainWindow(QtGui.QMainWindow):
Display the Advanced Key Management dialog.
"""
- domain = self._providers.get_selected_provider()
- logged_user = "{0}@{1}".format(self._logged_user, domain)
+ logged_user = self._login_widget.get_logged_user()
details = self._provider_details
mx_provided = False
@@ -570,8 +531,14 @@ class MainWindow(QtGui.QMainWindow):
Display the preferences window.
"""
- account = Account(self._logged_user,
- self._providers.get_selected_provider())
+ 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.show()
@@ -769,6 +736,10 @@ class MainWindow(QtGui.QMainWindow):
providers = self._settings.get_configured_providers()
self._providers.set_providers(providers)
+
+ provider = self._providers.get_selected_provider()
+ self._login_widget.set_provider(provider)
+
self._show_systray()
if not self._start_hidden:
@@ -809,7 +780,9 @@ class MainWindow(QtGui.QMainWindow):
self.eip_needs_login.emit()
self._wizard = None
- self._backend_connect(only_tracked=True)
+ # HACK FIX: disconnection of signals triggers a reconnection later
+ # chich segfaults on wizard quit
+ # self._backend_connect(only_tracked=True)
else:
domain = self._settings.get_provider()
if domain is not None:
@@ -872,12 +845,6 @@ class MainWindow(QtGui.QMainWindow):
self._eip_menu.setVisible(visible)
self._ui_eip_visible = visible
- def _set_label_offline(self):
- """
- Set the login label to reflect offline status.
- """
- # TODO: figure out what widget to use for this. Maybe the window title?
-
#
# systray
#
@@ -1129,76 +1096,19 @@ class MainWindow(QtGui.QMainWindow):
return not (has_provider_on_disk and skip_first_run)
@QtCore.Slot()
- def _download_provider_config(self):
+ def _disconnect_login_wait(self):
"""
- Start the bootstrapping sequence. It will download the
- provider configuration if it's not present, otherwise will
- emit the corresponding signals inmediately
- """
- self._disconnect_scheduled_login()
- domain = self._providers.get_selected_provider()
- self._backend.provider_setup(provider=domain)
-
- @QtCore.Slot(dict)
- def _load_provider_config(self, data):
- """
- TRIGGERS:
- self._backend.signaler.prov_download_provider_info
-
- Once the provider config has been downloaded, start the second
- part of the bootstrapping sequence.
-
- :param data: result from the last stage of the
- backend.provider_setup()
- :type data: dict
- """
- if data[PASSED_KEY]:
- selected_provider = self._providers.get_selected_provider()
- self._backend.provider_bootstrap(provider=selected_provider)
- else:
- logger.error(data[ERROR_KEY])
- self._login_problem_provider()
-
- @QtCore.Slot()
- def _login_problem_provider(self):
- """
- Warn the user about a problem with the provider during login.
- """
- # XXX triggers?
- self._login_widget.set_status(
- self.tr("Unable to login: Problem with provider"))
- self._login_widget.set_enabled(True)
-
- def _schedule_login(self):
- """
- Schedule the login sequence to go after the EIP started.
-
- The login sequence is connected to all finishing status of EIP
- (connected, disconnected, aborted or died) to continue with the login
- after EIP.
- """
- logger.debug('Login scheduled when eip_connected is triggered')
- eip_sigs = self._eip_conductor.qtsigs
- eip_sigs.connected_signal.connect(self._download_provider_config)
- eip_sigs.disconnected_signal.connect(self._download_provider_config)
- eip_sigs.connection_aborted_signal.connect(
- self._download_provider_config)
- eip_sigs.connection_died_signal.connect(self._download_provider_config)
-
- def _disconnect_scheduled_login(self):
- """
- Disconnect scheduled login signals if exists
+ Disconnect the EIP finishing signal to the wait flag on the login
+ widget.
"""
try:
eip_sigs = self._eip_conductor.qtsigs
- eip_sigs.connected_signal.disconnect(
- self._download_provider_config)
- eip_sigs.disconnected_signal.disconnect(
- self._download_provider_config)
- eip_sigs.connection_aborted_signal.disconnect(
- self._download_provider_config)
- eip_sigs.connection_died_signal.disconnect(
- self._download_provider_config)
+ slot = lambda: self._login_widget.wait_for_login(False)
+
+ eip_sigs.connected_signal.disconnect(slot)
+ eip_sigs.disconnected_signal.disconnect(slot)
+ eip_sigs.connection_aborted_signal.disconnect(slot)
+ eip_sigs.connection_died_signal.disconnect(slot)
except Exception:
# signal not connected
pass
@@ -1219,9 +1129,10 @@ class MainWindow(QtGui.QMainWindow):
# TODO: we should handle the case that EIP is autostarting since we
# won't get a warning until EIP has fully started.
# TODO: we need to add a check for the mail status (smtp/imap/soledad)
- something_runing = (self._logged_user is not None or
+ something_runing = (self._login_widget.get_logged_user() is not None or
self._already_started_eip)
provider = self._providers.get_selected_provider()
+ self._login_widget.set_provider(provider)
if not something_runing:
if wizard:
@@ -1271,118 +1182,35 @@ class MainWindow(QtGui.QMainWindow):
start the SRP authentication, and as the last step
bootstrapping the EIP service
"""
- # TODO most of this could ve handled by the login widget,
- provider = self._providers.get_selected_provider()
- if flags.OFFLINE is True:
- logger.debug("OFFLINE mode! bypassing remote login")
- # TODO reminder, we're not handling logout for offline
- # mode.
- self._login_widget.logged_in(provider)
- self._logged_in_offline = True
- self._set_label_offline()
- self.offline_mode_bypass_login.emit()
- else:
- self.ui.action_create_new_account.setEnabled(False)
- if self._login_widget.start_login(provider):
- if self._trying_to_start_eip:
- self._schedule_login()
- else:
- self._download_provider_config()
-
- @QtCore.Slot(unicode)
- def _authentication_error(self, msg):
- """
- TRIGGERS:
- Signaler.srp_auth_error
- Signaler.srp_auth_server_error
- Signaler.srp_auth_connection_error
- Signaler.srp_auth_bad_user_or_password
-
- Handle the authentication errors.
-
- :param msg: the message to show to the user.
- :type msg: unicode
- """
- self._login_widget.set_status(msg)
- self._login_widget.set_enabled(True)
- self.ui.action_create_new_account.setEnabled(True)
+ self.ui.action_create_new_account.setEnabled(False)
- @QtCore.Slot()
- def _cancel_login(self):
- """
- TRIGGERS:
- self._login_widget.cancel_login
-
- Stop the login sequence.
- """
- logger.debug("Cancelling log in.")
- self._disconnect_scheduled_login()
-
- self._cancel_ongoing_defers()
-
- # Needed in case of EIP starting and login deferer never set
- self._set_login_cancelled()
+ ok = self._login_widget.do_login()
+ if not ok:
+ logger.error("There was a problem triggering the login.")
+ return
def _cancel_ongoing_defers(self):
"""
Cancel the running defers to avoid app blocking.
"""
# XXX: Should we stop all the backend defers?
- self._backend.provider_cancel_setup()
- self._backend.user_cancel_login()
self._backend.soledad_cancel_bootstrap()
self._backend.soledad_close()
self._soledad_started = False
@QtCore.Slot()
- def _set_login_cancelled(self):
- """
- TRIGGERS:
- Signaler.prov_cancelled_setup fired by
- self._backend.provider_cancel_setup()
-
- Re-enable the login widget and display a message for
- the cancelled operation.
- """
- self._login_widget.set_status(self.tr("Log in cancelled by the user."))
- self._login_widget.set_enabled(True)
-
- @QtCore.Slot(dict)
- def _provider_config_loaded(self, data):
- """
- TRIGGERS:
- self._backend.signaler.prov_check_api_certificate
-
- Once the provider configuration is loaded, this starts the SRP
- authentication
- """
- if data[PASSED_KEY]:
- username = self._login_widget.get_user()
- password = self._login_widget.get_password()
-
- self._show_hide_unsupported_services()
-
- domain = self._providers.get_selected_provider()
- self._backend.user_login(provider=domain,
- username=username, password=password)
- else:
- logger.error(data[ERROR_KEY])
- self._login_problem_provider()
-
- @QtCore.Slot()
- def _authentication_finished(self):
+ def _on_user_logged_in(self):
"""
TRIGGERS:
- self._srp_auth.authentication_finished
+ self._login_widget.logged_in
Once the user is properly authenticated, try starting the EIP
- service
+ service.
"""
- self._login_widget.set_status(self.tr("Succeeded"), error=False)
+ self._disconnect_login_wait()
- self._logged_user = self._login_widget.get_user()
- user = self._logged_user
+ user = self._login_widget.get_logged_user()
domain = self._providers.get_selected_provider()
full_user_id = make_address(user, domain)
self._mail_conductor.userid = full_user_id
@@ -1400,15 +1228,25 @@ class MainWindow(QtGui.QMainWindow):
if MX_SERVICE not in self._provider_details['services']:
self._set_mx_visible(False)
+ @QtCore.Slot()
+ def _on_user_logged_out(self):
+ """
+ TRIGGER:
+ self._login_widget.logged_out
+
+ Switch the stackedWidget back to the login stage after
+ logging out
+ """
+ self._mail_conductor.stop_mail_services()
+ self._mail_status.mail_state_disabled()
+ self._show_hide_unsupported_services()
+
def _start_eip_bootstrap(self):
"""
Change the stackedWidget index to the EIP status one and
triggers the eip bootstrapping.
"""
-
domain = self._providers.get_selected_provider()
- self._login_widget.logged_in(domain)
-
self._enabled_services = self._settings.get_enabled_services(domain)
# TODO separate UI from logic.
@@ -1477,8 +1315,13 @@ class MainWindow(QtGui.QMainWindow):
return eip_enabled and eip_provided
+ @QtCore.Slot()
def _maybe_run_soledad_setup_checks(self):
"""
+ TRIGGERS:
+ self._eip_conductor.qtsigs.connected_signal
+ self._login_widget.login_offline_finished
+
Conditionally start Soledad.
"""
# TODO split.
@@ -1503,8 +1346,9 @@ class MainWindow(QtGui.QMainWindow):
self._backend.soledad_load_offline(username=full_user_id,
password=password, uuid=uuid)
else:
- if self._logged_user is not None:
- domain = self._providers.get_selected_provider()
+ logged_user = self._login_widget.get_logged_user()
+ if logged_user is not None:
+ username, domain = logged_user.split('@')
self._backend.soledad_bootstrap(username=username,
domain=domain,
password=password)
@@ -1552,6 +1396,30 @@ class MainWindow(QtGui.QMainWindow):
self._eip_status.enable_eip_start()
@QtCore.Slot()
+ def _on_eip_connecting(self):
+ """
+ TRIGGERS:
+ self._eip_conductor.qtsigs.connecting_signal
+
+ This is triggered when EIP starts connecting.
+
+ React to any EIP finishing signal[1] that means that the network is
+ ready to be used and trigger the "don't keep waiting" action on the
+ login widget.
+
+ [1] finishing signal => connected, disconnected, aborted or died
+ """
+ self._login_widget.wait_for_login(True)
+
+ eip_sigs = self._eip_conductor.qtsigs
+ slot = lambda: self._login_widget.wait_for_login(False)
+
+ eip_sigs.connected_signal.connect(slot)
+ eip_sigs.disconnected_signal.connect(slot)
+ eip_sigs.connection_aborted_signal.connect(slot)
+ eip_sigs.connection_died_signal.connect(slot)
+
+ @QtCore.Slot()
def _on_eip_connection_connected(self):
"""
TRIGGERS:
@@ -1674,8 +1542,7 @@ class MainWindow(QtGui.QMainWindow):
if not self._already_started_eip:
if EIP_SERVICE in self._enabled_services:
if missing_helpers:
- msg = self.tr(
- "Disabled: missing helper files")
+ msg = self.tr("Disabled: missing helper files")
else:
msg = self.tr("Not supported"),
self._eip_status.set_eip_status(msg, error=True)
@@ -1729,68 +1596,6 @@ class MainWindow(QtGui.QMainWindow):
# end of EIP methods ---------------------------------------------
- @QtCore.Slot()
- def _logout(self):
- """
- TRIGGERS:
- self._login_widget.logout
-
- Start the logout sequence
- """
- self._cancel_ongoing_defers()
-
- # XXX: If other defers are doing authenticated stuff, this
- # might conflict with those. CHECK!
- self._backend.user_logout()
- self.logout.emit()
-
- @QtCore.Slot()
- def _logout_error(self):
- """
- TRIGGER:
- self._srp_auth.logout_error
-
- Inform the user about a logout error.
- """
- self._login_widget.done_logout()
- self._login_widget.set_status(
- self.tr("Something went wrong with the logout."))
-
- @QtCore.Slot()
- def _logout_ok(self):
- """
- TRIGGER:
- self._srp_auth.logout_ok
-
- Switch the stackedWidget back to the login stage after
- logging out
- """
- self._login_widget.done_logout()
-
- self._logged_user = None
- self._login_widget.logged_out()
- self._mail_status.mail_state_disabled()
-
- self._show_hide_unsupported_services()
-
- @QtCore.Slot(dict)
- def _intermediate_stage(self, data):
- # TODO this method name is confusing as hell.
- """
- TRIGGERS:
- self._backend.signaler.prov_name_resolution
- self._backend.signaler.prov_https_connection
- self._backend.signaler.prov_download_ca_cert
-
- If there was a problem, display it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
- """
- passed = data[PASSED_KEY]
- if not passed:
- logger.error(data[ERROR_KEY])
- self._login_problem_provider()
-
#
# window handling methods
#
@@ -1842,9 +1647,8 @@ class MainWindow(QtGui.QMainWindow):
logger.debug('Stopping mail services')
self._mail_conductor.stop_mail_services()
- if self._logged_user is not None:
- logger.debug("Doing logout")
- self._backend.user_logout()
+ logger.debug("Doing logout")
+ self._login_widget.do_logout()
logger.debug('Terminating vpn')
self._backend.eip_stop(shutdown=True)
diff --git a/src/leap/bitmask/gui/signaltracker.py b/src/leap/bitmask/gui/signaltracker.py
new file mode 100644
index 00000000..c83359c4
--- /dev/null
+++ b/src/leap/bitmask/gui/signaltracker.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# signaltracker.py
+# Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
+import logging
+
+from PySide import QtCore
+
+logger = logging.getLogger(__name__)
+
+
+class SignalTracker(QtCore.QObject):
+ """
+ A class meant to be inherited from that helps to do the Qt connect and keep
+ track of the connections made, allowing to disconnect those tracked signals
+ as well.
+ """
+ def __init__(self):
+ # this list contains the connected signals that we want to keep track.
+ # each item of the list is a:
+ # tuple of (Qt signal, callable or Qt slot or Qt signal)
+ self._connected_signals = []
+
+ def connect_and_track(self, signal, method):
+ """
+ Connect the signal and keep track of it.
+
+ :param signal: the signal to connect to.
+ :type signal: QtCore.Signal
+ :param method: the method to call when the signal is triggered.
+ :type method: callable, Slot or Signal
+ """
+ if (signal, method) in self._connected_signals:
+ logger.warning("Signal already connected.")
+ return
+
+ self._connected_signals.append((signal, method))
+ signal.connect(method)
+
+ def disconnect_and_untrack(self):
+ """
+ Disconnect all the tracked signals.
+ """
+ for signal, method in self._connected_signals:
+ try:
+ signal.disconnect(method)
+ except (TypeError, RuntimeError) as e:
+ # most likely the signal was not connected
+ logger.warning("Disconnect error: {0!r}".format(e))
+ logger.warning("Signal: {0!r} -> {1!r}".format(signal, method))
+
+ self._connected_signals = []
diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui
index bfd5f9c0..9ee9a283 100644
--- a/src/leap/bitmask/gui/ui/login.ui
+++ b/src/leap/bitmask/gui/ui/login.ui
@@ -29,12 +29,12 @@
<string notr="true"/>
</property>
<layout class="QGridLayout" name="gridLayout">
- <property name="margin">
- <number>0</number>
- </property>
<property name="verticalSpacing">
<number>6</number>
</property>
+ <property name="margin">
+ <number>0</number>
+ </property>
<item row="2" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
@@ -71,6 +71,9 @@
</property>
<item row="3" column="1">
<widget class="QPushButton" name="btnLogin">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -134,6 +137,9 @@
<property name="inputMask">
<string/>
</property>
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
</widget>
</item>
</layout>
@@ -283,10 +289,47 @@
</customwidget>
</customwidgets>
<tabstops>
+ <tabstop>lnUser</tabstop>
+ <tabstop>lnPassword</tabstop>
<tabstop>chkRemember</tabstop>
+ <tabstop>btnLogin</tabstop>
+ <tabstop>btnLogout</tabstop>
</tabstops>
<resources>
<include location="../../../../../data/resources/icons.qrc"/>
</resources>
- <connections/>
+ <connections>
+ <connection>
+ <sender>lnPassword</sender>
+ <signal>returnPressed()</signal>
+ <receiver>btnLogin</receiver>
+ <slot>click()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>212</x>
+ <y>171</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>169</x>
+ <y>234</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lnUser</sender>
+ <signal>returnPressed()</signal>
+ <receiver>lnPassword</receiver>
+ <slot>setFocus()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>309</x>
+ <y>140</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>304</x>
+ <y>163</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
</ui>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index ff9cae55..35043a68 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -30,6 +30,7 @@ from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.gui.signaltracker import SignalTracker
from leap.bitmask.services import get_service_display_name, get_supported
from leap.bitmask.util.credentials import password_checks, username_checks
from leap.bitmask.util.credentials import USERNAME_REGEX
@@ -41,8 +42,7 @@ QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
-class Wizard(QtGui.QWizard):
-
+class Wizard(QtGui.QWizard, SignalTracker):
"""
First run wizard to register a user and setup a provider
"""
@@ -62,12 +62,11 @@ class Wizard(QtGui.QWizard):
:type backend: Backend
"""
QtGui.QWizard.__init__(self)
+ SignalTracker.__init__(self)
self.ui = Ui_Wizard()
self.ui.setupUi(self)
- self._connected_signals = []
-
self.setPixmap(QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(":/images/mask-icon.png"))
@@ -83,9 +82,9 @@ class Wizard(QtGui.QWizard):
self._use_existing_provider = False
self.ui.grpCheckProvider.setVisible(False)
- self._connect_and_track(self.ui.btnCheck.clicked, self._check_provider)
- self._connect_and_track(self.ui.lnProvider.returnPressed,
- self._check_provider)
+ conntrack = self.connect_and_track
+ conntrack(self.ui.btnCheck.clicked, self._check_provider)
+ conntrack(self.ui.lnProvider.returnPressed, self._check_provider)
self._leap_signaler = leap_signaler
@@ -97,27 +96,22 @@ class Wizard(QtGui.QWizard):
# this details are set when the provider download is complete.
self._provider_details = None
- self._connect_and_track(self.currentIdChanged,
- self._current_id_changed)
+ conntrack(self.currentIdChanged, self._current_id_changed)
- self._connect_and_track(self.ui.lnProvider.textChanged,
- self._enable_check)
- self._connect_and_track(self.ui.rbNewProvider.toggled,
- lambda x: self._enable_check())
- self._connect_and_track(self.ui.cbProviders.currentIndexChanged[int],
- self._reset_provider_check)
+ conntrack(self.ui.lnProvider.textChanged, self._enable_check)
+ conntrack(self.ui.rbNewProvider.toggled,
+ lambda x: self._enable_check())
+ conntrack(self.ui.cbProviders.currentIndexChanged[int],
+ self._reset_provider_check)
- self._connect_and_track(self.ui.lblUser.returnPressed,
- self._focus_password)
- self._connect_and_track(self.ui.lblPassword.returnPressed,
- self._focus_second_password)
- self._connect_and_track(self.ui.lblPassword2.returnPressed,
- self._register)
- self._connect_and_track(self.ui.btnRegister.clicked,
- self._register)
+ conntrack(self.ui.lblUser.returnPressed, self._focus_password)
+ conntrack(self.ui.lblPassword.returnPressed,
+ self._focus_second_password)
+ conntrack(self.ui.lblPassword2.returnPressed, self._register)
+ conntrack(self.ui.btnRegister.clicked, self._register)
- self._connect_and_track(self.ui.rbExistingProvider.toggled,
- self._skip_provider_checks)
+ conntrack(self.ui.rbExistingProvider.toggled,
+ self._skip_provider_checks)
usernameRe = QtCore.QRegExp(USERNAME_REGEX)
self.ui.lblUser.setValidator(
@@ -142,19 +136,7 @@ class Wizard(QtGui.QWizard):
self._provider_checks_ok = False
self._provider_setup_ok = False
- self._connect_and_track(self.finished, self._wizard_finished)
-
- def _connect_and_track(self, signal, method):
- """
- Helper to connect signals and keep track of them.
-
- :param signal: the signal to connect to.
- :type signal: QtCore.Signal
- :param method: the method to call when the signal is triggered.
- :type method: callable, Slot or Signal
- """
- self._connected_signals.append((signal, method))
- signal.connect(method)
+ conntrack(self.finished, self._wizard_finished)
@QtCore.Slot()
def _wizard_finished(self):
@@ -170,7 +152,8 @@ class Wizard(QtGui.QWizard):
self._provider_setup_ok = False
self.ui.lnProvider.setText('')
self.ui.grpCheckProvider.setVisible(False)
- self._disconnect_tracked()
+ # HACK FIX: disconnection of signals triggers a segfault on quit
+ # self.disconnect_and_untrack()
def _load_configured_providers(self):
"""
@@ -781,7 +764,7 @@ class Wizard(QtGui.QWizard):
Connects all the backend signals with the wizard.
"""
sig = self._leap_signaler
- conntrack = self._connect_and_track
+ conntrack = self.connect_and_track
conntrack(sig.prov_name_resolution, self._name_resolution)
conntrack(sig.prov_https_connection, self._https_connection)
conntrack(sig.prov_download_provider_info,
@@ -797,14 +780,3 @@ class Wizard(QtGui.QWizard):
conntrack(sig.srp_registration_finished, self._registration_finished)
conntrack(sig.srp_registration_failed, self._registration_failed)
conntrack(sig.srp_registration_taken, self._registration_taken)
-
- def _disconnect_tracked(self):
- """
- This method is called when the wizard dialog is closed.
- We disconnect all the signals in here.
- """
- for signal, method in self._connected_signals:
- try:
- signal.disconnect(method)
- except RuntimeError:
- pass # Signal was not connected
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index 1d6bb1d0..086927dd 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -56,19 +56,24 @@ init_signals = InitSignals()
def init_platform():
"""
- Return the right initializer for the platform we are running in, or
- None if no proper initializer is found
+ Run the right initializer for the platform we are running in.
+
+ :return: whether the initializacion succeeded or not.
+ :rtype: bool
"""
initializer = None
+ ok = False
try:
initializer = globals()[_system + "Initializer"]
except:
pass
if initializer:
- logger.debug("Running initializer for %s" % (platform.system(),))
- initializer()
+ logger.debug("Running initializer for %s" % (_system, ))
+ ok = initializer()
else:
- logger.debug("Initializer not found for %s" % (platform.system(),))
+ logger.debug("Initializer not found for %s" % (_system, ))
+
+ return ok
#
@@ -181,6 +186,38 @@ def check_missing():
if missing_some and not alert_missing and not complain_missing:
init_signals.eip_missing_helpers.emit()
+
+def check_polkit():
+ """
+ Check if we have a running polkit agent and tries to launch an agent if
+ needed.
+ Show an error message if there is no agent and we couldn't run one.
+
+ :return: True if we have a polkit running (or if we started one), False
+ otherwise
+ :rtype: bool
+ """
+ if LinuxPolicyChecker.is_up():
+ return True
+
+ try:
+ LinuxPolicyChecker.maybe_pkexec()
+ except Exception:
+ logger.error("No polkit agent running.")
+
+ msg = QtGui.QMessageBox()
+ msg.setWindowTitle(msg.tr("No polkit agent running"))
+ msg.setText(
+ msg.tr('There is no polkit agent running and it is needed to run '
+ 'the Bitmask services.<br>Take a look at the '
+ '<a href="https://leap.se/en/docs/client/known-issues">'
+ 'known issues</a> page'))
+ msg.setIcon(QtGui.QMessageBox.Critical)
+ msg.exec_()
+
+ return False
+
+
#
# windows initializers
#
@@ -377,6 +414,7 @@ def DarwinInitializer():
# Second check, for missing scripts.
check_missing()
+ return True
#
@@ -483,5 +521,13 @@ def LinuxInitializer():
Missing files can be either bitmask-root policykit file.
The dialog will also be raised if some of those files are
found to have incorrect permissions.
+
+ :return: whether the operations went ok or not, if False, it's recommended
+ not to continue with the app run.
+ :rtype: bool
"""
+ if not check_polkit():
+ return False
+
check_missing()
+ return True
diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py
index 416aff34..0fb9f4fa 100644
--- a/src/leap/bitmask/services/mail/conductor.py
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -207,6 +207,8 @@ class MailConductor(IMAPControl, SMTPControl):
IMAPControl.__init__(self)
SMTPControl.__init__(self)
+ self._mail_services_started = False
+
self._backend = backend
self._mail_machine = None
self._mail_connection = mail_connection.MailConnection()
@@ -264,10 +266,16 @@ class MailConductor(IMAPControl, SMTPControl):
self.start_smtp_service(download_if_needed=download_if_needed)
self.start_imap_service()
+ self._mail_services_started = True
+
def stop_mail_services(self):
"""
Stop the IMAP and SMTP services.
"""
+ if not self._mail_services_started:
+ logger.debug("Mail services not started.")
+ return
+
self.stop_imap_service()
self.stop_smtp_service()
if self._firewall is not None:
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index c4e43bfe..2044a27c 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -276,13 +276,14 @@ class SoledadBootstrapper(AbstractBootstrapper):
server_url, cert_file, token)
logger.debug("Soledad has been initialized.")
return
- except Exception:
+ except Exception as exc:
init_tries += 1
msg = "Init failed, retrying... (retry {0} of {1})".format(
init_tries, self.MAX_INIT_RETRIES)
logger.warning(msg)
continue
+ logger.exception(exc)
raise SoledadInitError()
def load_and_sync_soledad(self, uuid=None, offline=False):
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index cbd6d8a5..346caed5 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -74,9 +74,10 @@ def build_parser():
help='Verbosity level for openvpn logs [1-6]')
# mail stuff
- parser.add_argument('-o', '--offline', action="store_true",
- help='Starts Bitmask in offline mode: will not '
- 'try to sync with remote replicas for email.')
+ # XXX Disabled right now since it's not tested after login refactor
+ # parser.add_argument('-o', '--offline', action="store_true",
+ # help='Starts Bitmask in offline mode: will not '
+ # 'try to sync with remote replicas for email.')
parser.add_argument('--acct', metavar="user@provider",
nargs='?',
diff --git a/src/leap/bitmask/util/polkit_agent.py b/src/leap/bitmask/util/polkit_agent.py
index 7764f571..af5e431c 100644
--- a/src/leap/bitmask/util/polkit_agent.py
+++ b/src/leap/bitmask/util/polkit_agent.py
@@ -18,6 +18,7 @@
Daemonizes polkit authentication agent.
"""
import logging
+import os
import subprocess
import daemon
@@ -30,21 +31,52 @@ BASE_PATH_KDE = "/usr/lib/kde4/libexec/"
GNO_PATH = BASE_PATH_GNO + AUTH_FILE % ("gnome",)
KDE_PATH = BASE_PATH_KDE + AUTH_FILE % ("kde",)
+POLKIT_PATHS = {
+ '/usr/lib/lxpolkit/lxpolkit',
+ '/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1',
+ '/usr/lib/mate-polkit/polkit-mate-authentication-agent-1',
+ '/usr/lib/kde4/libexec/polkit-kde-authentication-agent-1',
+}
+
+
+def _get_polkit_agent():
+ """
+ Return a valid polkit agent to use.
+
+ :rtype: str or None
+ """
+ # TODO: in caso of having more than one polkit agent we may want to
+ # stablish priorities. E.g.: lxpolkit over gnome-polkit for minimalistic
+ # desktops.
+ for polkit in POLKIT_PATHS:
+ if os.path.isfile(polkit):
+ return polkit
+
+ return None
+
def _launch_agent():
+ """
+ Launch a polkit authentication agent on a subprocess.
+ """
+ polkit_agent = _get_polkit_agent()
+
+ if polkit_agent is None:
+ logger.erro("No usable polkit was found.")
+ return
+
logger.debug('Launching polkit auth agent')
try:
- subprocess.call(GNO_PATH)
- except Exception as exc:
- logger.error('Exception while running polkit authentication agent '
- '%s' % (exc,))
# XXX fix KDE launch. See: #3755
- # try:
- # subprocess.call(KDE_PATH)
- # except Exception as exc:
+ subprocess.call(polkit_agent)
+ except Exception as e:
+ logger.error('Error launching polkit authentication agent %r' % (e, ))
def launch():
+ """
+ Launch a polkit authentication agent as a daemon.
+ """
with daemon.DaemonContext():
_launch_agent()
diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py
index 68a1af28..65132133 100644
--- a/src/leap/bitmask/util/privilege_policies.py
+++ b/src/leap/bitmask/util/privilege_policies.py
@@ -149,7 +149,12 @@ class LinuxPolicyChecker(PolicyChecker):
"""
env = None
if flags.STANDALONE:
- env = {"PYTHONPATH": os.path.abspath('../../../../lib/')}
+ # This allows us to send to subprocess the environment configs that
+ # works for the standalone bundle (like the PYTHONPATH)
+ env = dict(os.environ)
+ # The LD_LIBRARY_PATH is set on the launcher but not forwarded to
+ # subprocess unless we do so explicitly.
+ env["LD_LIBRARY_PATH"] = os.path.abspath("./lib/")
try:
# We need to quote the command because subprocess call
# will do "sh -c 'foo'", so if we do not quoute it we'll end