diff options
author | Tomás Touceda <chiiph@leap.se> | 2013-03-06 15:43:36 -0300 |
---|---|---|
committer | Tomás Touceda <chiiph@leap.se> | 2013-03-06 16:05:46 -0300 |
commit | 361a18b0e727a68d6e0d1e9d03273630b9c14692 (patch) | |
tree | b7e578b369117395a79be5a6fcb396772184064c | |
parent | ee8fbbdc2f3dbccea3a830b40e9eb0be5b392d7b (diff) |
Add UI merging all the code
Also add resources
27 files changed, 2820 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..62e2fd80 --- /dev/null +++ b/Makefile @@ -0,0 +1,69 @@ +# ################################ +# Makefile for compiling resources +# files. +# TODO move to setup scripts +# and implement it in python +# http://die-offenbachs.homelinux.org:48888/hg/eric5/file/5072605ad4dd/compileUiFiles.py +###### EDIT ###################### + +#Directory with ui and resource files +RESOURCE_DIR = data/resources +UI_DIR = src/leap/gui/ui + +#Directory for compiled resources +COMPILED_DIR = src/leap/gui + +#Directory for (finished) translations +TRANSLAT_DIR = data/translations + +#Project file, used for translations +PROJFILE = data/leap_client.pro + +#UI files to compile +# UI_FILES = foo.ui +UI_FILES = mainwindow.ui wizard.ui +#Qt resource files to compile +#images.qrc +RESOURCES = mainwindow.qrc # locale.qrc + +#pyuic4 and pyrcc4 binaries +PYUIC = pyside-uic +PYRCC = pyside-rcc +PYLUP = pylupdate4 +LRELE = lrelease + + +################################# +# DO NOT EDIT FOLLOWING + +COMPILED_UI = $(UI_FILES:%.ui=$(COMPILED_DIR)/ui_%.py) +COMPILED_RESOURCES = $(RESOURCES:%.qrc=$(COMPILED_DIR)/%_rc.py) + +DEBVER = $(shell dpkg-parsechangelog | sed -ne 's,Version: ,,p') + +# + +all : resources ui + +resources : $(COMPILED_RESOURCES) + +ui : $(COMPILED_UI) + +translations: + $(PYLUP) $(PROJFILE) + $(LRELE) $(TRANSLAT_DIR)/*.ts + +$(COMPILED_DIR)/ui_%.py : $(UI_DIR)/%.ui + $(PYUIC) $< -o $@ + +$(COMPILED_DIR)/%_rc.py : $(RESOURCE_DIR)/%.qrc + $(PYRCC) $< -o $@ + +manpages: + rst2man docs/man/leap.1.rst docs/man/leap.1 + +apidocs: + @sphinx-apidoc -o docs/api src/leap + +clean : + $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/data/images/Arrow-Down-32.png b/data/images/Arrow-Down-32.png Binary files differnew file mode 100644 index 00000000..c5c607a1 --- /dev/null +++ b/data/images/Arrow-Down-32.png diff --git a/data/images/Arrow-Up-32.png b/data/images/Arrow-Up-32.png Binary files differnew file mode 100644 index 00000000..85370ac5 --- /dev/null +++ b/data/images/Arrow-Up-32.png diff --git a/data/images/Blue-Arrow-Right-32.png b/data/images/Blue-Arrow-Right-32.png Binary files differnew file mode 100644 index 00000000..66e50b0d --- /dev/null +++ b/data/images/Blue-Arrow-Right-32.png diff --git a/data/images/Dialog-accept.png b/data/images/Dialog-accept.png Binary files differnew file mode 100644 index 00000000..5a8a0bdb --- /dev/null +++ b/data/images/Dialog-accept.png diff --git a/data/images/Dialog-error.png b/data/images/Dialog-error.png Binary files differnew file mode 100644 index 00000000..51da2f5b --- /dev/null +++ b/data/images/Dialog-error.png diff --git a/data/images/Emblem-question.png b/data/images/Emblem-question.png Binary files differnew file mode 100644 index 00000000..b2163e5b --- /dev/null +++ b/data/images/Emblem-question.png diff --git a/data/images/Globe.png b/data/images/Globe.png Binary files differnew file mode 100644 index 00000000..7549433b --- /dev/null +++ b/data/images/Globe.png diff --git a/data/images/conn_connected.png b/data/images/conn_connected.png Binary files differnew file mode 100644 index 00000000..a5d20497 --- /dev/null +++ b/data/images/conn_connected.png diff --git a/data/images/conn_connecting.png b/data/images/conn_connecting.png Binary files differnew file mode 100644 index 00000000..31b6e617 --- /dev/null +++ b/data/images/conn_connecting.png diff --git a/data/images/conn_error.png b/data/images/conn_error.png Binary files differnew file mode 100644 index 00000000..85669af6 --- /dev/null +++ b/data/images/conn_error.png diff --git a/data/images/favicon.ico b/data/images/favicon.ico Binary files differnew file mode 100644 index 00000000..b5f3505a --- /dev/null +++ b/data/images/favicon.ico diff --git a/data/images/leap-client.icns b/data/images/leap-client.icns Binary files differnew file mode 100644 index 00000000..d5d52cdc --- /dev/null +++ b/data/images/leap-client.icns diff --git a/data/images/leap-color-big.png b/data/images/leap-color-big.png Binary files differnew file mode 100644 index 00000000..eafacdcd --- /dev/null +++ b/data/images/leap-color-big.png diff --git a/data/images/leap-color-small.png b/data/images/leap-color-small.png Binary files differnew file mode 100644 index 00000000..bc9d4e7f --- /dev/null +++ b/data/images/leap-color-small.png diff --git a/data/leap_client.pro b/data/leap_client.pro new file mode 100644 index 00000000..9ec1a43b --- /dev/null +++ b/data/leap_client.pro @@ -0,0 +1,11 @@ +# qmake file + +# is not there a f*** way of expanding this? other to template with python I mean... + +# SOURCES += ... +# where to generate ts files -- tx will pick from here + +# original file, english + +TRANSLATIONS += ts/en_US.ts + diff --git a/data/resources/locale.qrc b/data/resources/locale.qrc new file mode 100644 index 00000000..47fb5243 --- /dev/null +++ b/data/resources/locale.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> +<file>../translations/es.qm</file> +<file>../translations/de.qm</file> +</qresource> +</RCC> diff --git a/data/resources/mainwindow.qrc b/data/resources/mainwindow.qrc new file mode 100644 index 00000000..e53e3633 --- /dev/null +++ b/data/resources/mainwindow.qrc @@ -0,0 +1,16 @@ +<RCC> + <qresource prefix="/"> + <file>../images/Blue-Arrow-Right-32.png</file> + <file>../images/Globe.png</file> + <file>../images/conn_error.png</file> + <file>../images/leap-color-big.png</file> + <file>../images/Arrow-Down-32.png</file> + <file>../images/Arrow-Up-32.png</file> + <file>../images/conn_connecting.png</file> + <file>../images/conn_connected.png</file> + <file>../images/leap-color-small.png</file> + <file>../images/Dialog-accept.png</file> + <file>../images/Dialog-error.png</file> + <file>../images/Emblem-question.png</file> + </qresource> +</RCC> diff --git a/data/translations/README.rst b/data/translations/README.rst new file mode 100644 index 00000000..1f3dd0b3 --- /dev/null +++ b/data/translations/README.rst @@ -0,0 +1,8 @@ +data/translations +================= + +We expect finished translations (i.e., those downloaded from ``transifex``) to live here. + +Translator object will pick them from here. + +(Actually, from the embedded locale_rc) diff --git a/data/ts/README.rst b/data/ts/README.rst new file mode 100644 index 00000000..3db2d104 --- /dev/null +++ b/data/ts/README.rst @@ -0,0 +1,14 @@ +data/ts +======= + +Here we expect the .ts files generated by typing:: + + $ make translations + +Which will generate the sources (en_US) + +For uploading a source:: + + $ tx push -s + +Translator should pick finished ``.qm`` files from ``data/translations`` instead of this folder. diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts new file mode 100644 index 00000000..d2cba837 --- /dev/null +++ b/data/ts/en_US.ts @@ -0,0 +1,477 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS><TS version="2.0"> +<context> + <name>ConnectionPage</name> + <message> + <location filename="../src/leap/gui/firstrun/connect.py" line="26"/> + <source>Connecting...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/connect.py" line="27"/> + <source>Setting up a encrypted connection with the provider</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/connect.py" line="85"/> + <source>Getting EIP configuration files</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/connect.py" line="101"/> + <source>Authentication error: %s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/connect.py" line="109"/> + <source>Getting EIP certificate</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>EIPConductorAppMixin</name> + <message> + <location filename="../src/leap/baseapp/eip.py" line="221"/> + <source>&Disconnect</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/eip.py" line="235"/> + <source>&Connect</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>EIPErrors</name> + <message> + <location filename="../src/leap/eip/exceptions.py" line="66"/> + <source>We could not find any authentication agent in your system.<br/>Make sure you have <b>polkit-gnome-authentication-agent-1</b> running and try again.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="77"/> + <source>We could not find <b>pkexec</b> in your system.<br/> Do you want to try <b>setuid workaround</b>? (<i>DOES NOTHING YET</i>)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="88"/> + <source>No suitable openvpn command found. <br/>(Might be a permissions problem)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="97"/> + <source>there is a problem with provider certificate</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="104"/> + <source>an error occurred during configuratio of leap services</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="119"/> + <source>Server does not allow secure connections</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="126"/> + <source>Server certificate could not be verified</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="137"/> + <source>We could not find your eip certs in the expected path</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/eip/exceptions.py" line="111"/> + <source>Another OpenVPN Process has been detected. Please close it before starting leap-client</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Errors</name> + <message> + <location filename="../src/leap/base/exceptions.py" line="57"/> + <source>Interface not found</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/base/exceptions.py" line="64"/> + <source>Looks like your computer is not connected to the internet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/base/exceptions.py" line="72"/> + <source>Looks like there are problems with your internet connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/base/exceptions.py" line="80"/> + <source>It looks like there is no internet connection.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/base/exceptions.py" line="88"/> + <source>Domain cannot be found</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/base/exceptions.py" line="95"/> + <source>The Encrypted Connection was lost.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>IntroPage</name> + <message> + <location filename="../src/leap/gui/firstrun/intro.py" line="14"/> + <source>First run wizard</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/intro.py" line="37"/> + <source>Sign up for a new account</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/intro.py" line="40"/> + <source>Log In with my credentials</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/intro.py" line="24"/> + <source>Now we will guide you through some configuration that is needed before you can connect for the first time.<br><br>If you ever need to modify these options again, you can find the wizard in the '<i>Settings</i>' menu from the main window.<br><br>Do you want to <b>sign up</b> for a new account, or <b>log in</b> with an already existing username?<br></source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>LastPage</name> + <message> + <location filename="../src/leap/gui/firstrun/last.py" line="18"/> + <source>Connecting to Encrypted Internet Proxy service...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/last.py" line="66"/> + <source>Click '<i>%s</i>' to end the wizard and save your settings.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/last.py" line="62"/> + <source>You are now using an encrypted connection!</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>LogInPage</name> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="96"/> + <source>Username must be in the username@provider form.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="140"/> + <source>Resolving domain name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="163"/> + <source>Authentication error: %s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="179"/> + <source>Credentials validated.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="24"/> + <source>Log In</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="38"/> + <source>User &name:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="53"/> + <source>&Password:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="80"/> + <source>Log in</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/login.py" line="25"/> + <source>Log in with your credentials</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>LogPaneMixin</name> + <message> + <location filename="../src/leap/baseapp/log.py" line="25"/> + <source>&Connect</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/log.py" line="38"/> + <source>Disconnected</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProviderInfoPage</name> + <message> + <location filename="../src/leap/gui/firstrun/providerinfo.py" line="19"/> + <source>Provider Information</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerinfo.py" line="20"/> + <source>Services offered by this provider</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerinfo.py" line="95"/> + <source>enrollment policy</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProviderSetupValidationPage</name> + <message> + <location filename="../src/leap/gui/firstrun/providersetup.py" line="28"/> + <source>Provider setup</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providersetup.py" line="85"/> + <source>Fetching CA certificate</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providersetup.py" line="107"/> + <source>Checking CA fingerprint</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providersetup.py" line="125"/> + <source>Validating api certificate</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providersetup.py" line="29"/> + <source>Gathering configuration options for this provider</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>RegisterUserPage</name> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="31"/> + <source>Sign Up</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="208"/> + <source>Registration succeeded!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="244"/> + <source>Password does not match..</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="248"/> + <source>Password too short.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="253"/> + <source>Password too obvious.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="282"/> + <source>Error connecting to provider (timeout)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="287"/> + <source>Error Connecting to provider (connerr).</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="295"/> + <source>Error during registration (%s)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="305"/> + <source>Could not register (bad response)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="312"/> + <source>Username not available.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="48"/> + <source>User &name:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="60"/> + <source>&Password:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="66"/> + <source>Password (again):</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="72"/> + <source>&Remember username and password.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/register.py" line="378"/> + <source>Register a new user with provider <em>%s</em></source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>SelectProviderPage</name> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="32"/> + <source>Enter Provider</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="87"/> + <source>chec&k!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="97"/> + <source>Server certificate could not be verified.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="136"/> + <source>Certificate validation</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="290"/> + <source>Could not get info from provider.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="293"/> + <source>Could not download provider info (refused conn.).</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="103"/> + <source>&Trust this provider certificate.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="344"/> + <source>Do you want to <b>trust this provider certificate?</b></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="347"/> + <source>SHA-256 fingerprint: <i>%s</i><br></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="33"/> + <source>Please enter the domain of the provider you want to use for your connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="222"/> + <source>Checking if it is a valid provider</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="276"/> + <source>Checking for a secure connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/gui/firstrun/providerselect.py" line="303"/> + <source>Getting info from the provider</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>StatusAwareTrayIconMixin</name> + <message> + <location filename="../src/leap/baseapp/systray.py" line="78"/> + <source>EIP Connection Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="87"/> + <source><b>disconnected</b></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="126"/> + <source>Encryption ON turn &off</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="131"/> + <source>&Details...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="135"/> + <source>&About</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="138"/> + <source>About Q&t</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="141"/> + <source>&Quit</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="152"/> + <source>Encryption ON turn o&ff</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="157"/> + <source>Encryption OFF turn &on</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="162"/> + <source>connecting...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/baseapp/systray.py" line="185"/> + <source>About</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/src/leap/gui/__init__.py b/src/leap/gui/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/gui/__init__.py diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py new file mode 100644 index 00000000..1821e4a6 --- /dev/null +++ b/src/leap/gui/mainwindow.py @@ -0,0 +1,600 @@ +# -*- coding: utf-8 -*- +# mainwindow.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/>. + +""" +Main window for the leap client +""" +import os +import logging + +from PySide import QtCore, QtGui + +from ui_mainwindow import Ui_MainWindow +from leap.config.providerconfig import ProviderConfig +from leap.crypto.srpauth import SRPAuth +from leap.services.eip.vpn import VPN +from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services.eip.eipbootstrapper import EIPBootstrapper +from leap.services.eip.eipconfig import EIPConfig +from leap.gui.wizard import Wizard + +logger = logging.getLogger(__name__) + + +class MainWindow(QtGui.QMainWindow): + """ + Main window for login and presenting status updates to the user + """ + + # StackedWidget indexes + LOGIN_INDEX = 0 + EIP_STATUS_INDEX = 1 + + def __init__(self): + QtGui.QMainWindow.__init__(self) + + self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") + self.CONNECTED_ICON = QtGui.QPixmap(":/images/conn_connected.png") + self.ERROR_ICON = QtGui.QPixmap(":/images/conn_error.png") + + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + + self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password) + + self.ui.btnLogin.clicked.connect(self._login) + self.ui.lnUser.returnPressed.connect(self._focus_password) + self.ui.lnPassword.returnPressed.connect(self._login) + + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + + # This is loaded only once, there's a bug when doing that more + # than once + self._provider_config = ProviderConfig() + self._eip_config = EIPConfig() + # This is created once we have a valid provider config + self._srp_auth = None + + # This thread is always running, although it's quite + # lightweight when it's done setting up provider + # configuration and certificate. + self._provider_bootstrapper = ProviderBootstrapper() + + # TODO: add sigint handler + + # Intermediate stages, only do something if there was an error + self._provider_bootstrapper.name_resolution.connect( + self._intermediate_stage) + self._provider_bootstrapper.https_connection.connect( + self._intermediate_stage) + self._provider_bootstrapper.download_ca_cert.connect( + self._intermediate_stage) + + # Important stages, loads the provider config and checks + # certificates + self._provider_bootstrapper.download_provider_info.connect( + self._load_provider_config) + self._provider_bootstrapper.check_api_certificate.connect( + self._provider_config_loaded) + + # This thread is similar to the provider bootstrapper + self._eip_bootstrapper = EIPBootstrapper() + + self._eip_bootstrapper.download_config.connect( + self._intermediate_stage) + self._eip_bootstrapper.download_client_certificate.connect( + self._start_eip) + + self._vpn = VPN() + self._vpn.state_changed.connect(self._update_vpn_state) + self._vpn.status_changed.connect(self._update_vpn_status) + + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._vpn.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._provider_bootstrapper.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._eip_bootstrapper.set_should_quit) + + self.ui.action_sign_out.setEnabled(False) + self.ui.action_sign_out.triggered.connect(self._logout) + self.ui.action_about_leap.triggered.connect(self._about) + self.ui.action_quit.triggered.connect(self.quit) + self.ui.action_wizard.triggered.connect(self._launch_wizard) + + # Used to differentiate between real quits and close to tray + self._really_quit = False + + self._systray = None + self._action_visible = QtGui.QAction("Hide", self) + self._action_visible.triggered.connect(self._toggle_visible) + + self._center_window() + self._wizard = None + if self._first_run(): + self._wizard = Wizard() + # Give this window time to finish init and then show the wizard + QtCore.QTimer.singleShot(1, self._launch_wizard) + self._wizard.finished.connect(self._finish_init) + else: + self._finish_init() + + def _launch_wizard(self): + if self._wizard is None: + self._wizard = Wizard() + self._wizard.exec_() + + def _finish_init(self): + self.ui.cmbProviders.addItems(self._configured_providers()) + self._show_systray() + self.show() + if self._wizard: + possible_username = self._wizard.get_username() + if possible_username is not None: + self.ui.lnUser.setText(possible_username) + self._focus_password() + self._wizard = None + + def _show_systray(self): + """ + Sets up the systray icon + """ + systrayMenu = QtGui.QMenu(self) + systrayMenu.addAction(self._action_visible) + systrayMenu.addAction(self.ui.action_sign_out) + systrayMenu.addSeparator() + systrayMenu.addAction(self.ui.action_quit) + self._systray = QtGui.QSystemTrayIcon(self) + self._systray.setContextMenu(systrayMenu) + self._systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) + self._systray.setVisible(True) + self._systray.activated.connect(self._toggle_visible) + + def _toggle_visible(self): + """ + SLOT + TRIGGER: self._systray.activated + + Toggles the window visibility + """ + self.setVisible(not self.isVisible()) + action_visible_text = "Hide" + if not self.isVisible(): + action_visible_text = "Show" + self._action_visible.setText(action_visible_text) + + def _center_window(self): + """ + Centers the mainwindow based on the desktop geometry + """ + app = QtGui.QApplication.instance() + width = app.desktop().width() + height = app.desktop().height() + window_width = self.size().width() + window_height = self.size().height() + x = (width / 2.0) - (window_width / 2.0) + y = (height / 2.0) - (window_height / 2.0) + self.move(x, y) + + def _about(self): + """ + Display the About LEAP dialog + """ + QtGui.QMessageBox.about(self, "About LEAP", + "LEAP is a non-profit dedicated to giving " + "all internet users access to secure " + "communication. Our focus is on adapting " + "encryption technology to make it easy to use " + "and widely available. " + "<a href=\"https://leap.se\">More about LEAP" + "</a>") + + def quit(self): + self._really_quit = True + if self._wizard: + self._wizard.accept() + self.close() + + def changeEvent(self, e): + """ + Reimplements the changeEvent method to minimize to tray + """ + if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ + e.type() == QtCore.QEvent.WindowStateChange and \ + self.isMinimized(): + self._toggle_visible() + e.accept() + return + QtGui.QMainWindow.changeEvent(self, e) + + def closeEvent(self, e): + """ + Reimplementation of closeEvent to close to tray + """ + if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ + not self._really_quit: + self._toggle_visible() + e.ignore() + return + QtGui.QMainWindow.closeEvent(self, e) + + def _configured_providers(self): + """ + Returns the available providers based on the file structure + + @rtype: list + """ + providers = os.listdir( + os.path.join(self._provider_config.get_path_prefix(), + "leap", + "providers")) + return providers + + def _first_run(self): + """ + Returns True if there are no configured providers. False otherwise + + @rtype: bool + """ + return len(self._configured_providers()) == 0 + + def _focus_password(self): + """ + Focuses in the password lineedit + """ + self.ui.lnPassword.setFocus() + + def _set_status(self, status): + """ + Sets the status label at the login stage to status + + @param status: status message + @type status: str + """ + self.ui.lblStatus.setText(status) + + def _set_eip_status(self, status): + """ + Sets the status label at the VPN stage to status + + @param status: status message + @type status: str + """ + self.ui.lblEIPStatus.setText(status) + + def _login_set_enabled(self, enabled=False): + """ + Enables or disables all the login widgets + + @param enabled: wether they should be enabled or not + @type enabled: bool + """ + self.ui.lnUser.setEnabled(enabled) + self.ui.lnPassword.setEnabled(enabled) + self.ui.btnLogin.setEnabled(enabled) + self.ui.chkRemember.setEnabled(enabled) + self.ui.cmbProviders.setEnabled(enabled) + + def _download_provider_config(self): + """ + Starts the bootstrapping sequence. It will download the + provider configuration if it's not present, otherwise will + emit the corresponding signals inmediately + """ + provider = self.ui.cmbProviders.currentText() + + self._provider_bootstrapper.start() + self._provider_bootstrapper.run_provider_select_checks( + provider, + download_if_needed=True) + + def _load_provider_config(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_provider_info + + Once the provider config has been downloaded, this loads the + self._provider_config instance with it and starts the second + part of the bootstrapping sequence + + @param data: result from the last stage of the + run_provider_select_checks + @type data: dict + """ + if data[self._provider_bootstrapper.PASSED_KEY]: + provider = self.ui.cmbProviders.currentText() + if self._provider_config.loaded() or \ + self._provider_config.load(os.path.join("leap", + "providers", + provider, + "provider.json")): + self._provider_bootstrapper.run_provider_setup_checks( + self._provider_config, + download_if_needed=True) + else: + self._set_status("Could not load provider configuration") + self._login_set_enabled(True) + else: + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_set_enabled(True) + + def _login(self): + """ + SLOT + TRIGGERS: + self.ui.btnLogin.clicked + self.ui.lnPassword.returnPressed + + Starts the login sequence. Which involves bootstrapping the + selected provider if the selection is valid (not empty), then + start the SRP authentication, and as the last step + bootstrapping the EIP service + """ + assert self._provider_config, "We need a provider config" + + username = self.ui.lnUser.text() + password = self.ui.lnPassword.text() + provider = self.ui.cmbProviders.currentText() + + if len(provider) == 0: + self._set_status("Please select a valid provider") + return + + if len(username) == 0: + self._set_status("Please provide a valid username") + return + + if len(password) == 0: + self._set_status("Please provide a valid Password") + return + + self._set_status("Logging in...") + self._login_set_enabled(False) + + self._download_provider_config() + + def _provider_config_loaded(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.check_api_certificate + + Once the provider configuration is loaded, this starts the SRP + authentication + """ + assert self._provider_config, "We need a provider config!" + + self._provider_bootstrapper.set_should_quit() + + if data[self._provider_bootstrapper.PASSED_KEY]: + username = self.ui.lnUser.text() + password = self.ui.lnPassword.text() + + if self._srp_auth is None: + self._srp_auth = SRPAuth(self._provider_config) + self._srp_auth.authentication_finished.connect( + self._authentication_finished) + self._srp_auth.logout_finished.connect( + self._done_logging_out) + + self._srp_auth.authenticate(username, password) + else: + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_set_enabled(True) + + def _authentication_finished(self, ok, message): + """ + SLOT + TRIGGER: self._srp_auth.authentication_finished + + Once the user is properly authenticated, try starting the EIP + service + """ + self._set_status(message) + if ok: + self.ui.action_sign_out.setEnabled(True) + # We leave a bit of room for the user to see the + # "Succeeded" message and then we switch to the EIP status + # panel + QtCore.QTimer.singleShot(1000, self._switch_to_status) + else: + self._login_set_enabled(True) + + def _switch_to_status(self): + """ + Changes the stackedWidget index to the EIP status one and + triggers the eip bootstrapping + """ + self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) + self._download_eip_config() + + def _download_eip_config(self): + """ + Starts the EIP bootstrapping sequence + """ + assert self._eip_bootstrapper, "We need an eip bootstrapper!" + assert self._provider_config, "We need a provider config" + + self._set_eip_status("Checking configuration, please wait...") + + if self._provider_config.provides_eip(): + self._eip_bootstrapper.start() + self._eip_bootstrapper.run_eip_setup_checks( + self._provider_config, + download_if_needed=True) + else: + self._set_eip_status("%s does not support EIP" % + (self._provider_config.get_domain(),)) + + def _set_eip_status_icon(self, status): + """ + Given a status step from the VPN thread, set the icon properly + + @param status: status step + @type status: str + """ + selected_pixmap = self.ERROR_ICON + if status in ("AUTH", "GET_CONFIG"): + selected_pixmap = self.CONNECTING_ICON + elif status in ("CONNECTED"): + selected_pixmap = self.CONNECTED_ICON + + self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) + self._systray.setIcon(QtGui.QIcon(selected_pixmap)) + + def _update_vpn_state(self, data): + """ + SLOT + TRIGGER: self._vpn.state_changed + + Updates the displayed VPN state based on the data provided by + the VPN thread + """ + status = data[self._vpn.STATUS_STEP_KEY] + self._set_eip_status_icon(status) + if status == "AUTH": + self._set_eip_status("VPN: Authenticating...") + elif status == "GET_CONFIG": + self._set_eip_status("VPN: Retrieving configuration...") + elif status == "CONNECTED": + self._set_eip_status("VPN: Connected!") + else: + self._set_eip_status(status) + + def _update_vpn_status(self, data): + """ + SLOT + TRIGGER: self._vpn.status_changed + + Updates the download/upload labels based on the data provided + by the VPN thread + """ + upload = float(data[self._vpn.TUNTAP_WRITE_KEY]) + upload = upload / 1000.0 + self.ui.lblUpload.setText("%s Kb" % (upload,)) + download = float(data[self._vpn.TUNTAP_READ_KEY]) + download = download / 1000.0 + self.ui.lblDownload.setText("%s Kb" % (download,)) + + def _start_eip(self, data): + """ + SLOT + TRIGGER: self._eip_bootstrapper.download_client_certificate + + Starts the VPN thread if the eip configuration is properly + loaded + """ + assert self._eip_config, "We need an eip config!" + assert self._provider_config, "We need a provider config!" + + self._eip_bootstrapper.set_should_quit() + if self._eip_config.loaded() or \ + self._eip_config.load(os.path.join("leap", + "providers", + self._provider_config + .get_domain(), + "eip-service.json")): + self._vpn.start(eipconfig=self._eip_config, + providerconfig=self._provider_config, + socket_host="/home/chiiph/vpnsock", + socket_port="unix") + # TODO: display a message if the EIP configuration cannot be + # loaded + + def _logout(self): + """ + SLOT + TRIGGER: self.ui.action_sign_out.triggered + + Starts the logout sequence + """ + self._set_eip_status_icon("error") + self._set_eip_status("Signing out...") + self._srp_auth.logout() + + def _done_logging_out(self, ok, message): + """ + SLOT + TRIGGER: self._srp_auth.logout_finished + + Switches the stackedWidget back to the login stage after + logging out + """ + self._set_status(message) + self.ui.action_sign_out.setEnabled(False) + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + self.ui.lnPassword.setText("") + self._login_set_enabled(True) + self._set_status("") + self._vpn.set_should_quit() + + def _intermediate_stage(self, data): + """ + SLOT + TRIGGERS: + self._provider_bootstrapper.name_resolution + self._provider_bootstrapper.https_connection + self._provider_bootstrapper.download_ca_cert + self._eip_bootstrapper.download_config + + If there was a problem, displays it, otherwise it does nothing. + This is used for intermediate bootstrapping stages, in case + they fail. + """ + passed = data[self._provider_bootstrapper.PASSED_KEY] + if not passed: + self._login_set_enabled(True) + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + +if __name__ == "__main__": + import signal + from functools import partial + + def sigint_handler(*args, **kwargs): + logger.debug('SIGINT catched. shutting down...') + mainwindow = args[0] + mainwindow.quit() + + import sys + + logger = logging.getLogger(name='leap') + logger.setLevel(logging.DEBUG) + console = logging.StreamHandler() + console.setLevel(logging.DEBUG) + formatter = logging.Formatter( + '%(asctime)s ' + '- %(name)s - %(levelname)s - %(message)s') + console.setFormatter(formatter) + logger.addHandler(console) + + app = QtGui.QApplication(sys.argv) + mainwindow = MainWindow() + mainwindow.show() + + timer = QtCore.QTimer() + timer.start(500) + timer.timeout.connect(lambda: None) + + sigint = partial(sigint_handler, mainwindow) + signal.signal(signal.SIGINT, sigint) + + sys.exit(app.exec_()) diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui new file mode 100644 index 00000000..a527eaf6 --- /dev/null +++ b/src/leap/gui/ui/mainwindow.ui @@ -0,0 +1,377 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>405</width> + <height>579</height> + </rect> + </property> + <property name="windowTitle"> + <string>LEAP</string> + </property> + <property name="windowIcon"> + <iconset resource="../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/leap-color-big.png</normaloff>:/images/leap-color-big.png</iconset> + </property> + <property name="inputMethodHints"> + <set>Qt::ImhHiddenText</set> + </property> + <property name="iconSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="2"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0" colspan="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="9" column="2"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0" colspan="5"> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>1</number> + </property> + <widget class="QWidget" name="page"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="2" column="2" colspan="2"> + <widget class="QLineEdit" name="lnUser"/> + </item> + <item row="4" column="2"> + <widget class="QCheckBox" name="chkRemember"> + <property name="text"> + <string>Remember</string> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QPushButton" name="btnLogin"> + <property name="text"> + <string>Login</string> + </property> + </widget> + </item> + <item row="2" column="4"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string><b>Provider:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string><b>Password:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="2" colspan="2"> + <widget class="QComboBox" name="cmbProviders"/> + </item> + <item row="3" column="2" colspan="2"> + <widget class="QLineEdit" name="lnPassword"> + <property name="inputMask"> + <string/> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string><b>User:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0" colspan="5"> + <widget class="QLabel" name="lblStatus"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="1" column="0" colspan="6"> + <widget class="QLabel" name="lblEIPStatus"> + <property name="text"> + <string>Disconnected</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="0" colspan="6"> + <widget class="QLabel" name="lblVPNStatusIcon"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/conn_error.png</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="lblUpload"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0.0 Kb</string> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="QLabel" name="lblDownload"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0.0 Kb</string> + </property> + </widget> + </item> + <item row="2" column="5"> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Arrow-Up-32.png</pixmap> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Arrow-Down-32.png</pixmap> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="1" column="3" colspan="2"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="label"> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/leap-color-big.png</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>405</width> + <height>25</height> + </rect> + </property> + <widget class="QMenu" name="menuSession"> + <property name="title"> + <string>&Session</string> + </property> + <addaction name="action_sign_out"/> + <addaction name="separator"/> + <addaction name="action_quit"/> + </widget> + <widget class="QMenu" name="menuHelp"> + <property name="title"> + <string>Help</string> + </property> + <addaction name="action_help"/> + <addaction name="separator"/> + <addaction name="action_about_leap"/> + </widget> + <widget class="QMenu" name="menuSettings"> + <property name="title"> + <string>S&ettings</string> + </property> + <addaction name="action_wizard"/> + </widget> + <addaction name="menuSession"/> + <addaction name="menuSettings"/> + <addaction name="menuHelp"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <action name="action_sign_out"> + <property name="text"> + <string>&Sign out</string> + </property> + </action> + <action name="action_quit"> + <property name="text"> + <string>&Quit</string> + </property> + </action> + <action name="action_about_leap"> + <property name="text"> + <string>About &LEAP</string> + </property> + </action> + <action name="action_help"> + <property name="text"> + <string>&Help</string> + </property> + </action> + <action name="action_wizard"> + <property name="text"> + <string>&Wizard</string> + </property> + </action> + </widget> + <tabstops> + <tabstop>lnUser</tabstop> + <tabstop>lnPassword</tabstop> + <tabstop>chkRemember</tabstop> + <tabstop>btnLogin</tabstop> + <tabstop>cmbProviders</tabstop> + </tabstops> + <resources> + <include location="../../../../data/resources/mainwindow.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui new file mode 100644 index 00000000..86f8d458 --- /dev/null +++ b/src/leap/gui/ui/wizard.ui @@ -0,0 +1,800 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Wizard</class> + <widget class="QWizard" name="Wizard"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>536</width> + <height>452</height> + </rect> + </property> + <property name="windowTitle"> + <string>LEAP First run</string> + </property> + <property name="windowIcon"> + <iconset resource="../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/leap-color-big.png</normaloff>:/images/leap-color-big.png</iconset> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <property name="wizardStyle"> + <enum>QWizard::ModernStyle</enum> + </property> + <property name="options"> + <set>QWizard::IndependentPages</set> + </property> + <widget class="QWizardPage" name="introduction_page"> + <property name="title"> + <string>Welcome</string> + </property> + <property name="subTitle"> + <string>This is the LEAP Client first run wizard</string> + </property> + <attribute name="pageId"> + <string notr="true">0</string> + </attribute> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="0"> + <widget class="QRadioButton" name="rdoLogin"> + <property name="text"> + <string>Log In with my credentials</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string><html><head/><body><p>New we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Settings'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html></string> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="rdoRegister"> + <property name="text"> + <string>Sign up for a new account</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="verticalSpacer_11"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="4" column="0"> + <spacer name="verticalSpacer_12"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="WizardPage" name="select_provider_page"> + <property name="title"> + <string>Provider selection</string> + </property> + <property name="subTitle"> + <string>Please enter the domain of the provider you want to user for your connection</string> + </property> + <attribute name="pageId"> + <string notr="true">1</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>60</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>https://</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="lnProvider"/> + </item> + <item row="3" column="0" colspan="3"> + <widget class="QGroupBox" name="grpCheckProvider"> + <property name="title"> + <string>Checking provider</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Download provider information</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>HTTPS Connection</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="lblProviderInfo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="lblHTTPS"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="lblNameResolution"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Name resolution</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="btnCheck"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWizardPage" name="provider_info_page"> + <property name="title"> + <string>Provider Information</string> + </property> + <property name="subTitle"> + <string>Services offered by this provider</string> + </property> + <attribute name="pageId"> + <string notr="true">2</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="1" colspan="2"> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string><b>Enrollment policy:</b></string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QLabel" name="lblProviderURL"> + <property name="text"> + <string>URL</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="lblProviderName"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <spacer name="horizontalSpacer_7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <spacer name="verticalSpacer_13"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="5" column="2"> + <widget class="QLabel" name="lblProviderPolicy"> + <property name="text"> + <string>policy</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="3"> + <widget class="QLabel" name="lblProviderDesc"> + <property name="text"> + <string>Desc</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="0"> + <spacer name="verticalSpacer_14"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="WizardPage" name="setup_provider_page"> + <property name="title"> + <string>Provider setup</string> + </property> + <property name="subTitle"> + <string>Gathering configuration options for this provider</string> + </property> + <attribute name="pageId"> + <string notr="true">3</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Checking provider</string> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="2" column="1"> + <widget class="QLabel" name="lblCheckCaFpr"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="lblDownloadCaCert"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Download CA Certificate</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Check CA Certificate Fingerprint</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Check API Certificate</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="lblCheckApiCert"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>60</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0"> + <spacer name="verticalSpacer_8"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="WizardPage" name="register_user_page"> + <property name="title"> + <string>Register new user</string> + </property> + <property name="subTitle"> + <string>Register a new user with provider</string> + </property> + <attribute name="pageId"> + <string notr="true">4</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_7"> + <item row="1" column="0" colspan="3"> + <widget class="QLabel" name="lblRegisterStatus"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="6" column="1"> + <spacer name="verticalSpacer_7"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string><b>User:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string><b>Password:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1" colspan="2"> + <widget class="QLineEdit" name="lblPassword"/> + </item> + <item row="4" column="1" colspan="2"> + <widget class="QLineEdit" name="lblPassword2"/> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QLineEdit" name="lblUser"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string><b>Re-enter password:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QPushButton" name="btnRegister"> + <property name="text"> + <string>Register</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>60</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="WizardPage" name="eip_setup_page"> + <property name="title"> + <string>EIP Setup</string> + </property> + <property name="subTitle"> + <string>Setting up Encrypted Internet</string> + </property> + <attribute name="pageId"> + <string notr="true">5</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_8"> + <item row="0" column="0"> + <spacer name="verticalSpacer_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Checking EIP</string> + </property> + <layout class="QGridLayout" name="gridLayout_9"> + <item row="1" column="0"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Download EIP configuration</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>Download client certificate</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="lblDownloadEIPConfig"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="lblDownloadClientCert"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <spacer name="verticalSpacer_6"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWizardPage" name="finish_page"> + <property name="title"> + <string>Congratulations!</string> + </property> + <property name="subTitle"> + <string>You have successfully configured the LEAP client.</string> + </property> + <attribute name="pageId"> + <string notr="true">6</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_10"> + <item row="1" column="0"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <spacer name="verticalSpacer_9"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/leap-color-big.png</pixmap> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="label_25"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Globe.png</pixmap> + </property> + </widget> + </item> + <item row="3" column="1"> + <spacer name="verticalSpacer_10"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + <customwidgets> + <customwidget> + <class>WizardPage</class> + <extends>QWizardPage</extends> + <header>wizardpage.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>lblUser</tabstop> + <tabstop>lblPassword</tabstop> + <tabstop>lblPassword2</tabstop> + <tabstop>btnRegister</tabstop> + <tabstop>rdoRegister</tabstop> + <tabstop>rdoLogin</tabstop> + <tabstop>lnProvider</tabstop> + <tabstop>btnCheck</tabstop> + </tabstops> + <resources> + <include location="../../../../data/resources/mainwindow.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py new file mode 100644 index 00000000..7dcc8dd6 --- /dev/null +++ b/src/leap/gui/wizard.py @@ -0,0 +1,403 @@ +# -*- coding: utf-8 -*- +# wizard.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/>. + +""" +First run wizard +""" +import os +import logging + +from PySide import QtCore, QtGui + +from ui_wizard import Ui_Wizard +from leap.config.providerconfig import ProviderConfig +from leap.crypto.srpregister import SRPRegister +from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services.eip.eipbootstrapper import EIPBootstrapper + +logger = logging.getLogger(__name__) + + +class Wizard(QtGui.QWizard): + """ + First run wizard to register a user and setup a provider + """ + + INTRO_PAGE = 0 + SELECT_PROVIDER_PAGE = 1 + PRESENT_PROVIDER_PAGE = 2 + SETUP_PROVIDER_PAGE = 3 + REGISTER_USER_PAGE = 4 + SETUP_EIP_PAGE = 5 + FINISH_PATH = 6 + + WEAK_PASSWORDS = ("1234", "12345", "123456", + "password") + + def __init__(self): + QtGui.QWizard.__init__(self) + + self.ui = Ui_Wizard() + self.ui.setupUi(self) + + self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") + self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") + self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") + + self._show_register = False + + self.ui.grpCheckProvider.setVisible(False) + self.ui.btnCheck.clicked.connect(self._check_provider) + self.ui.lnProvider.returnPressed.connect(self._check_provider) + + self._provider_bootstrapper = ProviderBootstrapper() + self._provider_bootstrapper.name_resolution.connect( + self._name_resolution) + self._provider_bootstrapper.https_connection.connect( + self._https_connection) + self._provider_bootstrapper.download_provider_info.connect( + self._download_provider_info) + + self._provider_bootstrapper.download_ca_cert.connect( + self._download_ca_cert) + self._provider_bootstrapper.check_ca_fingerprint.connect( + self._check_ca_fingerprint) + self._provider_bootstrapper.check_api_certificate.connect( + self._check_api_certificate) + + self._eip_bootstrapper = EIPBootstrapper() + + self._eip_bootstrapper.download_config.connect( + self._download_eip_config) + self._eip_bootstrapper.download_client_certificate.connect( + self._download_client_certificate) + + self._domain = None + self._provider_config = ProviderConfig() + + self.currentIdChanged.connect(self._current_id_changed) + + self.ui.lblPassword.setEchoMode(QtGui.QLineEdit.Password) + self.ui.lblPassword2.setEchoMode(QtGui.QLineEdit.Password) + + self.ui.lblUser.returnPressed.connect( + self._focus_password) + self.ui.lblPassword.returnPressed.connect( + self._focus_second_password) + self.ui.lblPassword2.returnPressed.connect( + self._register) + self.ui.btnRegister.clicked.connect( + self._register) + + self._username = None + + def __del__(self): + self._provider_bootstrapper.set_should_quit() + self._eip_bootstrapper.set_should_quit() + self._provider_bootstrapper.wait() + self._eip_bootstrapper.wait() + + def get_username(self): + return self._username + + def _focus_password(self): + """ + Focuses at the password lineedit for the registration page + """ + self.ui.lblPassword.setFocus() + + def _focus_second_password(self): + """ + Focuses at the second password lineedit for the registration page + """ + self.ui.lblPassword2.setFocus() + + def _basic_password_checks(self, username, password, password2): + """ + Performs basic password checks to avoid really easy passwords. + + @param username: username provided at the registrarion form + @type username: str + @param password: password from the registration form + @type password: str + @param password2: second password from the registration form + @type password: str + + @return: returns True if all the checks pass, False otherwise + @rtype: bool + """ + message = None + + try: + username.encode("ascii") + password.encode("ascii") + except: + message = u"Refrain from using non ASCII áñ characters" + + if message is not None and password != password2: + message = "Passwords don't match" + + if message is not None and len(password) < 4: + message = "Password too short" + + if message is not None and password in self.WEAK_PASSWORDS: + message = "Password too easy" + + if message is not None and username == password: + message = "Password equal to username" + + if message is not None: + self._set_register_status(message) + self._focus_password() + return False + + return True + + def _register(self): + """ + Performs the registration based on the values provided in the form + """ + self.ui.btnRegister.setEnabled(False) + # See the disabled button + while QtGui.QApplication.instance().hasPendingEvents(): + QtGui.QApplication.instance().processEvents() + self.button(QtGui.QWizard.NextButton).setFocus() + + username = self.ui.lblUser.text() + password = self.ui.lblPassword.text() + password2 = self.ui.lblPassword2.text() + + if self._basic_password_checks(username, password, password2): + register = SRPRegister(provider_config=self._provider_config) + ok, req = register.register_user(username, password) + if ok: + self._set_register_status("<b>User registration OK</b>") + self._username = username + self.ui.lblPassword2.clearFocus() + # Detach this call to allow UI updates briefly + QtCore.QTimer.singleShot(1, + self.page(self.REGISTER_USER_PAGE) + .set_completed) + else: + print req.content + error_msg = "Unknown error" + try: + error_msg = req.json().get("errors").get("login")[0] + except: + logger.error("Unknown error: %r" % (req.content,)) + self._set_register_status(error_msg) + self.ui.btnRegister.setEnabled(True) + else: + self.ui.btnRegister.setEnabled(True) + + def _set_register_status(self, status): + """ + Sets the status label in the registration page to status + + @param status: status message to display, can be HTML + @type status: str + """ + self.ui.lblRegisterStatus.setText(status) + + def _check_provider(self): + """ + SLOT + TRIGGERS: + self.ui.btnCheck.clicked + self.ui.lnProvider.returnPressed + + Starts the checks for a given provider + """ + self.ui.grpCheckProvider.setVisible(True) + self.ui.btnCheck.setEnabled(False) + self._domain = self.ui.lnProvider.text() + + self._provider_bootstrapper.start() + self._provider_bootstrapper.run_provider_select_checks(self._domain) + + def _complete_task(self, data, label, complete=False, complete_page=-1): + """ + Checks a task and completes a page if specified + + @param data: data as it comes from the bootstrapper thread for + a specific check + @type data: dict + @param label: label that displays the status icon for a + specific check that corresponds to the data + @type label: QtGui.QLabel + @param complete: if True, it completes the page specified, + which must be of type WizardPage + @type complete: bool + @param complete_page: page id to complete + @type complete_page: int + """ + passed = data[self._provider_bootstrapper.PASSED_KEY] + error = data[self._provider_bootstrapper.ERROR_KEY] + if passed: + label.setPixmap(self.OK_ICON) + if complete: + self.page(complete_page).set_completed() + self.button(QtGui.QWizard.NextButton).setFocus() + else: + label.setPixmap(self.ERROR_ICON) + logger.error(error) + + def _name_resolution(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.name_resolution + + Sets the status for the name resolution check + """ + self._complete_task(data, self.ui.lblNameResolution) + + def _https_connection(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.https_connection + + Sets the status for the https connection check + """ + self._complete_task(data, self.ui.lblHTTPS) + + def _download_provider_info(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_provider_info + + Sets the status for the provider information download + check. Since this check is the last of this set, it also + completes the page if passed + """ + if self._provider_config.load(os.path.join("leap", + "providers", + self._domain, + "provider.json")): + self._complete_task(data, self.ui.lblProviderInfo, + True, self.SELECT_PROVIDER_PAGE) + else: + new_data = { + self._provider_bootstrapper.PASSED_KEY: False, + self._provider_bootstrapper.ERROR_KEY: + "Unable to load provider configuration" + } + self._complete_task(new_data, self.ui.lblProviderInfo) + + self.ui.btnCheck.setEnabled(True) + + def _download_ca_cert(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_ca_cert + + Sets the status for the download of the CA certificate check + """ + self._complete_task(data, self.ui.lblDownloadCaCert) + + def _check_ca_fingerprint(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.check_ca_fingerprint + + Sets the status for the CA fingerprint check + """ + self._complete_task(data, self.ui.lblCheckCaFpr) + + def _check_api_certificate(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.check_api_certificate + + Sets the status for the API certificate check. Also finishes + the provider bootstrapper thread since it's not needed anymore + from this point on, unless the whole check chain is restarted + """ + self._complete_task(data, self.ui.lblCheckApiCert, + True, self.SETUP_PROVIDER_PAGE) + self._provider_bootstrapper.set_should_quit() + + def _download_eip_config(self, data): + """ + SLOT + TRIGGER: self._eip_bootstrapper.download_config + + Sets the status for the EIP config downloading check + """ + self._complete_task(data, self.ui.lblDownloadEIPConfig) + + def _download_client_certificate(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_client_certificate + + Sets the status for the download client certificate check and + completes the page if passed. Also stops the eip bootstrapper + thread since it's not needed from this point on unless the + check chain is restarted + """ + self._complete_task(data, self.ui.lblDownloadClientCert, + True, self.SETUP_EIP_PAGE) + self._eip_bootstrapper.set_should_quit() + + def _current_id_changed(self, pageId): + """ + SLOT + TRIGGER: self.currentIdChanged + + Prepares the pages when they appear + """ + if pageId == self.SELECT_PROVIDER_PAGE: + self.ui.grpCheckProvider.setVisible(False) + self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) + self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) + self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) + + if pageId == self.SETUP_PROVIDER_PAGE: + self._provider_bootstrapper.\ + run_provider_setup_checks(self._provider_config) + + if pageId == self.SETUP_EIP_PAGE: + self._eip_bootstrapper.start() + self._eip_bootstrapper.run_eip_setup_checks(self._provider_config) + + if pageId == self.PRESENT_PROVIDER_PAGE: + # TODO: get the right lang for these + self.ui.lblProviderName.setText( + "<b>%s</b>" % + (self._provider_config.get_name(),)) + self.ui.lblProviderURL.setText(self._provider_config.get_domain()) + self.ui.lblProviderDesc.setText( + "<i>%s</i>" % + (self._provider_config.get_description(),)) + self.ui.lblProviderPolicy.setText(self._provider_config + .get_enrollment_policy()) + + def nextId(self): + """ + Sets the next page id for the wizard based on wether the user + wants to register a new identity or uses an existing one + """ + if self.currentPage() == self.page(self.INTRO_PAGE): + self._show_register = self.ui.rdoRegister.isChecked() + + if self.currentPage() == self.page(self.SETUP_PROVIDER_PAGE): + if self._show_register: + return self.REGISTER_USER_PAGE + else: + return self.SETUP_EIP_PAGE + + return QtGui.QWizard.nextId(self) diff --git a/src/leap/gui/wizardpage.py b/src/leap/gui/wizardpage.py new file mode 100644 index 00000000..2138ac7b --- /dev/null +++ b/src/leap/gui/wizardpage.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# wizardpage.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/>. + +from PySide import QtGui + + +class WizardPage(QtGui.QWizardPage): + """ + Simple wizard page helper + """ + + def __init__(self): + QtGui.QWizardPage.__init__(self) + self._completed = False + + def set_completed(self): + self._completed = True + self.completeChanged.emit() + + def isComplete(self): + return self._completed + + def cleanupPage(self): + self._completed = False + QtGui.QWizardPage.cleanupPage(self) |