Skip to content

Commit

Permalink
add letsconnect support #134
Browse files Browse the repository at this point in the history
  • Loading branch information
gijzelaerr committed Aug 8, 2018
1 parent 433b6ec commit 5b45c94
Show file tree
Hide file tree
Showing 60 changed files with 437 additions and 213 deletions.
Binary file modified doc/flow.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 11 additions & 6 deletions eduvpn/actions/add.py
Expand Up @@ -11,18 +11,23 @@
logger = logging.getLogger(__name__)


def new_provider(builder, verifier, secure_internet_uri, institute_access_uri):
def new_provider(builder, verifier, secure_internet_uri, institute_access_uri, lets_connect):
"""The connection type selection step"""
logger.info("add configuration clicked")
meta = Metadata()

# lets connect mode only supports custom URL
if lets_connect:
custom_url(builder=builder, meta=meta, verifier=verifier, lets_connect=lets_connect)
return

dialog = builder.get_object('connection-type-dialog')
window = builder.get_object('eduvpn-window')
dialog.set_transient_for(window)
dialog.show_all()
response = dialog.run()
dialog.hide()

meta = Metadata()

if response == 0: # cancel
logger.info("cancel button pressed")
return
Expand All @@ -31,14 +36,14 @@ def new_provider(builder, verifier, secure_internet_uri, institute_access_uri):
logger.info("secure button pressed")
meta.connection_type = 'Secure Internet'
meta.discovery_uri = secure_internet_uri
fetch_instance_step(meta=meta, builder=builder, verifier=verifier)
fetch_instance_step(meta=meta, builder=builder, verifier=verifier, lets_connect=lets_connect)

elif response == 2:
logger.info("institute button pressed")
meta.connection_type = 'Institute Access'
meta.discovery_uri = institute_access_uri
fetch_instance_step(meta=meta, builder=builder, verifier=verifier)
fetch_instance_step(meta=meta, builder=builder, verifier=verifier, lets_connect=lets_connect)

elif response == 3:
logger.info("custom button pressed")
custom_url(builder=builder, meta=meta, verifier=verifier)
custom_url(builder=builder, meta=meta, verifier=verifier, lets_connect=lets_connect)
2 changes: 1 addition & 1 deletion eduvpn/actions/add.pyi
Expand Up @@ -3,4 +3,4 @@
# Copyright: 2017, The Commons Conservancy eduVPN Programme
# SPDX-License-Identifier: GPL-3.0+

def new_provider(builder, verifier, secure_internet_uri: str, institute_access_uri: str): ...
def new_provider(builder, verifier, secure_internet_uri: str, institute_access_uri: str, lets_connect: bool): ...
6 changes: 3 additions & 3 deletions eduvpn/actions/delete.py
Expand Up @@ -10,13 +10,13 @@
from eduvpn.manager import delete_provider
from eduvpn.notify import notify
from eduvpn.util import error_helper, metadata_of_selected
from eduvpn.steps.provider import update_providers
from eduvpn.steps.start import refresh_start


logger = logging.getLogger(__name__)


def delete_profile(builder):
def delete_profile(builder, lets_connect):
"""called when the user presses the - button"""
logger.info("delete provider clicked")
meta = metadata_of_selected(builder)
Expand All @@ -40,7 +40,7 @@ def delete_profile(builder):
error_helper(window, "can't delete profile", str(e))
dialog.hide()
raise
GLib.idle_add(lambda: update_providers(builder))
GLib.idle_add(lambda: refresh_start(builder, lets_connect=lets_connect))
elif response == Gtk.ResponseType.NO:
logger.info("not deleting provider config")
dialog.hide()
2 changes: 1 addition & 1 deletion eduvpn/actions/delete.pyi
Expand Up @@ -3,4 +3,4 @@
# Copyright: 2017, The Commons Conservancy eduVPN Programme
# SPDX-License-Identifier: GPL-3.0+

def delete_profile(builder): ...
def delete_profile(builder, lets_connect: bool) -> None: ...
13 changes: 10 additions & 3 deletions eduvpn/actions/select.py
Expand Up @@ -10,12 +10,14 @@
from eduvpn.config import icon_size
from eduvpn.manager import is_provider_connected
from eduvpn.steps.messages import fetch_messages
from eduvpn.images import eduvpn_main_logo, letsconnect_main_logo


logger = logging.getLogger(__name__)


def select_profile(builder, verifier):
# ui thread
def select_profile(builder, verifier, lets_connect):
"""called when a users selects a configuration"""
messages_label = builder.get_object('messages-label')
notebook = builder.get_object('outer-notebook')
Expand All @@ -30,6 +32,11 @@ def select_profile(builder, verifier):
profile_image = builder.get_object('profile-image')
meta = metadata_of_selected(builder)

if lets_connect:
logo = letsconnect_main_logo
else:
logo = eduvpn_main_logo

if not meta:
logger.info("no configuration selected, showing main logo")
notebook.set_current_page(0)
Expand All @@ -41,7 +48,7 @@ def select_profile(builder, verifier):
icon = bytes2pixbuf(base64.b64decode(meta.icon_data.encode()),
width=icon_size['width'] * 2, height=icon_size['height'] * 2)
else:
_, icon = get_pixbuf()
_, icon = get_pixbuf(logo)
profile_image.set_from_pixbuf(icon)
profile_label.set_text(meta.connection_type)
profile_name_label.set_text(meta.profile_display_name)
Expand All @@ -67,7 +74,7 @@ def select_profile(builder, verifier):

messages_label.set_markup("")
if meta.token:
fetch_messages(meta=meta, builder=builder, verifier=verifier)
fetch_messages(meta=meta, builder=builder, verifier=verifier, lets_connect=lets_connect)
else:
logger.warning("no token available so not fetching messages")

Expand Down
5 changes: 4 additions & 1 deletion eduvpn/actions/select.pyi
Expand Up @@ -3,4 +3,7 @@
# Copyright: 2017, The Commons Conservancy eduVPN Programme
# SPDX-License-Identifier: GPL-3.0+

def select_profile(builder, verifier): ...
from typing import Optional
from eduvpn.metadata import Metadata

def select_profile(builder, verifier, lets_connect: bool) -> Optional[Metadata]: ...
6 changes: 6 additions & 0 deletions eduvpn/images.py
@@ -0,0 +1,6 @@
from eduvpn.util import get_prefix

prefix = get_prefix()

eduvpn_main_logo = prefix + "/share/icons/hicolor/128x128/apps/eduvpn-client.png"
letsconnect_main_logo = prefix + "/share/icons/hicolor/128x128/apps/letsconnect.png"
3 changes: 3 additions & 0 deletions eduvpn/images.pyi
@@ -0,0 +1,3 @@

eduvpn_main_logo = ... # type: str
letsconnect_main_logo = ... # type: str
23 changes: 16 additions & 7 deletions eduvpn/main.py
Expand Up @@ -31,6 +31,7 @@ def parse_args():
parser.add_argument('-d', '--debug', action='store_true', help="enable debug logging")
parser.add_argument('-v', '--version', action='store_true', help="print version and exit")
parser.add_argument('-t', '--test', action='store_true', help="use test discovery servers")
parser.add_argument('-l', '--lets_connect', action='store_true', help="Enable 'Let's Connect!' mode")
args = parser.parse_args()

if args.version:
Expand All @@ -46,14 +47,16 @@ def parse_args():

if args.test:
logger.warning("using test discovery URLs")
return level, config.secure_internet_uri_dev, config.institute_access_uri_dev, config.verify_key_dev
return level, config.secure_internet_uri_dev, config.institute_access_uri_dev, config.verify_key_dev,\
args.lets_connect
else:
logger.debug("using production discovery URLs")
return level, config.secure_internet_uri, config.institute_access_uri, config.verify_key
return level, config.secure_internet_uri, config.institute_access_uri, config.verify_key, args.lets_connect


def init():
level, secure_internet_uri, institute_access_uri, verify_key = parse_args()
def init(lets_connect):
level, secure_internet_uri, institute_access_uri, verify_key, lets_connect_arg = parse_args()
lets_connect = lets_connect or lets_connect_arg

if geteuid() == 0:
logger.error("Running eduVPN client as root is not supported (yet)")
Expand All @@ -70,12 +73,18 @@ def init():

edu_vpn_app = EduVpnApp(secure_internet_uri=secure_internet_uri,
institute_access_uri=institute_access_uri,
verify_key=verify_key)
verify_key=verify_key, lets_connect=lets_connect)
edu_vpn_app.run()
return edu_vpn_app


def main():
init()
def main_eduvpn():
init(lets_connect=False)
Gtk.main()
return 0


def main_lets_connect():
init(lets_connect=True)
Gtk.main()
return 0
7 changes: 4 additions & 3 deletions eduvpn/main.pyi
Expand Up @@ -6,7 +6,8 @@
from typing import Tuple
from eduvpn.ui import EduVpnApp

def main() -> int: ...
def main_eduvpn() -> int: ...
def main_lets_connect() -> int: ...

def parse_args() -> Tuple[str, str, str, str]: ...
def init() -> EduVpnApp: ...
def parse_args() -> Tuple[str, str, str, str, bool]: ...
def init(lets_connect: bool) -> EduVpnApp: ...
21 changes: 13 additions & 8 deletions eduvpn/oauth2.py
Expand Up @@ -10,6 +10,7 @@
from future.moves.urllib.parse import urlparse, parse_qs
from requests_oauthlib import OAuth2Session
from eduvpn.util import get_prefix
from eduvpn.images import eduvpn_main_logo, letsconnect_main_logo

logger = logging.getLogger(__name__)

Expand All @@ -18,7 +19,7 @@
<html lang=en>
<head>
<meta charset=utf-8>
<title>eduVPN - bye</title>
<title>{brand} - bye</title>
<style>
.center {{
font-family: arial;
Expand Down Expand Up @@ -63,7 +64,7 @@ def get_open_port():
return port


def one_request(port, timeout=None):
def one_request(port, lets_connect, timeout=None):
"""
Listen for one http request on port, then close and return request query
Expand All @@ -79,8 +80,13 @@ def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
logo = stringify_image()
self.wfile.write(landing_page.format(logo=logo).encode('utf-8'))
if lets_connect:
logo = stringify_image(letsconnect_main_logo)
content = landing_page.format(logo=logo, brand="Let's Connect!").encode('utf-8')
else:
logo = stringify_image(eduvpn_main_logo)
content = landing_page.format(logo=logo, brand='eduVPN').encode('utf-8')
self.wfile.write(content)
self.server.path = self.path

httpd = HTTPServer(('', port), RequestHandler)
Expand All @@ -97,9 +103,8 @@ def do_GET(self):
return parse_qs(parsed.query)


def stringify_image():
def stringify_image(logo):
import base64
logo = get_prefix() + "/share/eduvpn/eduvpn.png"
return base64.b64encode(open(logo, 'rb').read()).decode('ascii')


Expand All @@ -118,7 +123,7 @@ def create_oauth_session(port, auto_refresh_url):
return oauth


def get_oauth_token_code(port, timeout=None):
def get_oauth_token_code(port, lets_connect, timeout=None):
"""
Start webserver, open browser, wait for callback response.
Expand All @@ -128,7 +133,7 @@ def get_oauth_token_code(port, timeout=None):
str: the response code given by redirect
"""
logger.info("waiting for callback on port {}".format(port))
response = one_request(port, timeout)
response = one_request(port, lets_connect, timeout)
if 'code' in response and 'state' in response:
code = response['code'][0]
state = response['state'][0]
Expand Down
8 changes: 4 additions & 4 deletions eduvpn/oauth2.pyi
Expand Up @@ -3,15 +3,15 @@
# Copyright: 2017, The Commons Conservancy eduVPN Programme
# SPDX-License-Identifier: GPL-3.0+

from typing import Any
from typing import Any, Optional
from eduvpn.metadata import Metadata

landing_page = ... # type: str
client_id = ... # type: str
scope = ... # type: Any

def get_open_port(): ...
def one_request(port: int): ...
def get_open_port() -> int: ...
def one_request(port: int, lets_connect: bool, timeout: Optional[int]=None) -> dict: ...
def create_oauth_session(port :int, auto_refresh_url: str): ...
def get_oauth_token_code(port: int, timeout: int = None): ...
def get_oauth_token_code(port: int, lets_connect: bool, timeout: int = None): ...
def oauth_from_token(meta: Metadata): ...
28 changes: 16 additions & 12 deletions eduvpn/steps/browser.py
Expand Up @@ -19,16 +19,16 @@
logger = logging.getLogger(__name__)


def browser_step(builder, meta, verifier, force_token_refresh=False):
def browser_step(builder, meta, verifier, lets_connect, force_token_refresh=False):
"""The notorious browser step. if no token, starts webserver, wait for callback, show token dialog"""
logger.info("opening token dialog")
dialog = builder.get_object('token-dialog')
thread_helper(lambda: _phase1_background(meta=meta, dialog=dialog, verifier=verifier, builder=builder,
force_token_refresh=force_token_refresh))
force_token_refresh=force_token_refresh, lets_connect=lets_connect))
dialog.show_all()


def _phase1_background(meta, dialog, verifier, builder, force_token_refresh):
def _phase1_background(meta, dialog, verifier, builder, force_token_refresh, lets_connect):
try:
logger.info("starting token obtaining in background")
r = get_instance_info(instance_uri=meta.instance_base_uri, verifier=verifier)
Expand Down Expand Up @@ -59,16 +59,19 @@ def _phase1_background(meta, dialog, verifier, builder, force_token_refresh):
GLib.idle_add(lambda: dialog.hide())
raise
else:
GLib.idle_add(lambda: _phase1_callback(meta, port, code_verifier, oauth, auth_url, dialog, builder, state))
GLib.idle_add(lambda: _phase1_callback(meta, port, code_verifier, oauth, auth_url, dialog, builder, state,
lets_connect=lets_connect))
else:
logger.info("we already have a token, skipping browser step")
oauth = oauth_from_token(meta=meta)
GLib.idle_add(lambda: _phase2_callback(meta=meta, oauth=oauth, dialog=dialog, builder=builder))
GLib.idle_add(lambda: _phase2_callback(meta=meta, oauth=oauth, dialog=dialog, builder=builder,
lets_connect=lets_connect))


def _phase1_callback(meta, port, code_verifier, oauth, auth_url, dialog, builder, state):
def _phase1_callback(meta, port, code_verifier, oauth, auth_url, dialog, builder, state, lets_connect):
thread_helper(lambda: _phase2_background(meta=meta, port=port, oauth=oauth, code_verifier=code_verifier,
auth_url=auth_url, dialog=dialog, builder=builder, state=state))
auth_url=auth_url, dialog=dialog, builder=builder, state=state,
lets_connect=lets_connect))
_show_dialog(dialog, auth_url, builder)


Expand Down Expand Up @@ -96,13 +99,13 @@ def _show_dialog(dialog, auth_url, builder):
break


def _phase2_background(meta, port, oauth, code_verifier, auth_url, dialog, builder, state):
def _phase2_background(meta, port, oauth, code_verifier, auth_url, dialog, builder, state, lets_connect):
session = random()
logger.info("opening browser with url {}".format(auth_url))
try:
webbrowser.open(auth_url)
dialog.session = session
code, other_state = get_oauth_token_code(port, timeout=120)
code, other_state = get_oauth_token_code(port, lets_connect=lets_connect, timeout=120)
logger.info("control returned by browser")
if state != other_state:
logger.error("received from state, expected: {}, received: {}".format(state, other_state))
Expand All @@ -118,12 +121,13 @@ def _phase2_background(meta, port, oauth, code_verifier, auth_url, dialog, build
logging.error(error)
raise
else:
GLib.idle_add(lambda: _phase2_callback(meta=meta, oauth=oauth, dialog=dialog, builder=builder))
GLib.idle_add(lambda: _phase2_callback(meta=meta, oauth=oauth, dialog=dialog, builder=builder,
lets_connect=lets_connect))


def _phase2_callback(meta, oauth, dialog, builder):
def _phase2_callback(meta, oauth, dialog, builder, lets_connect):
logger.info("hiding url and token dialog")
url_dialog = builder.get_object('redirecturl-dialog')
GLib.idle_add(lambda: url_dialog.hide())
GLib.idle_add(lambda: dialog.hide())
fetch_profile_step(meta=meta, oauth=oauth, builder=builder)
fetch_profile_step(meta=meta, oauth=oauth, builder=builder, lets_connect=lets_connect)

0 comments on commit 5b45c94

Please sign in to comment.