Commit d25b5b4b authored by Stavros Sachtouris's avatar Stavros Sachtouris Committed by Giorgos Korfiatis
Browse files

Logout remote connection after getting credentials

When a user clicks on "Get credentials", a new window appears
where users can authenticate themselves. It is a remote window
implementing whatever mechanism the target cloud implements.

When the authentication window opens, the "Get credentials"
button disappears.

After the authentication is complete or canceled, the following
operations take place synchronously and the order they appear
bellow:
- if the authentication window is open, close it
- remove all cookies related to the account and pithos UI URLs
- Reactivate the "Get credentials" button.
parent 9fd926d6
__version__ = '0.1'
__version__ = '0.1'
import cmd
import sys
import logging
import json
from titanic import setup, syncer
from titanic.pithos_client import PithosFileClient
from titanic.localfs_client import FilesystemFileClient
import config
from agkyra.syncer import setup, syncer
from agkyra.syncer.pithos_client import PithosFileClient
from agkyra.syncer.localfs_client import LocalfsFileClient
from agkyra import config
# from config import AgkyraConfig
logging.basicConfig(filename='agkyra.log', level=logging.DEBUG)
LOG = logging.getLogger(__name__)
LOG.addHandler(logging.FileHandler('%s/agkyra.log' % config.AGKYRA_DIR))
LOG.setLevel(logging.CRITICAL)
SYNCER_LOG = logging.getLogger('agkyra.syncer')
SYNCER_LOG.addHandler(logging.FileHandler('%s/agkyra.log' % config.AGKYRA_DIR))
SYNCER_LOG.setLevel(logging.CRITICAL)
setup.GLOBAL_SETTINGS_NAME = config.AGKYRA_DIR
......@@ -41,11 +45,12 @@ class AgkyraCLI(cmd.Cmd):
# Init syncer
master = PithosFileClient(self.settings)
slave = FilesystemFileClient(self.settings)
slave = LocalfsFileClient(self.settings)
self.syncer = syncer.FileSyncer(self.settings, master, slave)
def preloop(self):
"""This runs when the shell loads"""
print 'Loading Agkyra (sometimes this takes a while)'
if not self.is_shell:
self.is_shell = True
self.prompt = '\xe2\x9a\x93 '
......@@ -128,14 +133,20 @@ class AgkyraCLI(cmd.Cmd):
def do_start(self, line):
"""Start syncing"""
self.syncer.run()
if not getattr(self, '_syncer_initialized', False):
self.syncer.probe_and_sync_all()
self._syncer_initialized = True
if self.syncer.paused:
self.syncer.start_decide()
def do_pause(self, line):
"""Pause syncing"""
if not self.syncer.paused:
self.syncer.pause_decide()
def do_status(self, line):
"""Get current status (running/paused, progress)"""
print 'I have no idea'
print 'paused' if self.syncer.paused else 'running'
# def do_shell(self, line):
# """Run system, shell commands"""
......@@ -181,4 +192,4 @@ class AgkyraCLI(cmd.Cmd):
# AgkyraCLI().run_onecmd(sys.argv)
# or run a shell with
AgkyraCLI().cmdloop()
# AgkyraCLI().cmdloop()
from wsgiref.simple_server import make_server
# from ws4py.websocket import EchoWebSocket
from protocol import WebSocketProtocol
from agkyra.protocol import WebSocketProtocol
from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler
from ws4py.server.wsgiutils import WebSocketWSGIApplication
from ws4py.client import WebSocketBaseClient
......@@ -20,11 +20,10 @@ LOG = logging.getLogger(__name__)
class GUI(WebSocketBaseClient):
"""Launch the GUI when the helper server is ready"""
def __init__(self, addr, gui_exec_path, gui_id):
def __init__(self, addr, gui_id):
"""Initialize the GUI Launcher"""
super(GUI, self).__init__(addr)
self.addr = addr
self.gui_exec_path = gui_exec_path
self.gui_id = gui_id
self.start = self.connect
......@@ -39,11 +38,10 @@ class GUI(WebSocketBaseClient):
with NamedTemporaryFile(delete=False) as fp:
json.dump(dict(gui_id=self.gui_id, address=self.addr), fp)
# subprocess.call blocks the execution
LOG.debug('RUN: %s %s' % (self.gui_exec_path, fp.name))
LOG.debug('RUN: %s' % (fp.name))
subprocess.call([
'/home/saxtouri/node-webkit-v0.11.6-linux-x64/nw',
# self.gui_exec_path,
abspath('gui/gui.nw'),
abspath('agkyra/nwjs/nw'),
abspath('agkyra/gui.nw'),
fp.name,
'--data-path', abspath('~/.agkyra')])
LOG.debug('GUI process closed, remove temp file')
......@@ -85,11 +83,11 @@ class HelperServer(object):
t.join()
def run(gui_exec_path):
def run():
"""Prepare helper and GUI and run them in the proper order"""
server = HelperServer()
addr = 'ws://localhost:%s' % server.port
gui = GUI(addr, gui_exec_path, server.gui_id)
gui = GUI(addr, server.gui_id)
LOG.info('Start helper server')
server.start()
......
......@@ -4,6 +4,7 @@
<body>
<script src="protocol.js"></script>
<script src="settings.js"></script>
<script src="static/js/jquery.js"></script>
<script type="text/javascript">
// Setup GUI
......@@ -42,7 +43,7 @@ menu.append(new gui.MenuItem({type: 'separator'}));
pause_item = new gui.MenuItem({
// pause menu item
icon: 'images/play_pause.png',
label: '',
label: 'NOT READY',
type: 'normal',
click: function() {
if (paused) {post_start(socket);} else {post_pause(socket);}
......@@ -51,84 +52,42 @@ pause_item = new gui.MenuItem({
pause_item.enabled = false;
menu.append(pause_item);
// Update progress
window.setInterval(function() {
var status = globals['status'];
var new_progress = progress_item.label;
var new_pause = pause_item.label;
var menu_modified = false;
if (status['paused'] !== null) {
switch(pause_item.label) {
case pause_syncing: if (status['paused']) {
// Update to "Paused - start syncing"
paused = true;
new_pause = start_syncing;
menu_modified = true;
} // else continue syncing
new_progress = status['progress'] + '%' + ' synced';
break;
case start_syncing: if (status['paused']) return;
// else update to "Syncing - pause syncing"
paused = false;
new_pause = pause_syncing;
new_progress = status['progress'] + '%' + ' synced';
menu_modified = true;
break;
default:
if (status['paused']) {new_pause = start_syncing; paused=true;}
else {new_pause = pause_syncing; paused=false;}
new_progress = status['progress'] + '%' + ' synced';
pause_item.enabled = true;
menu_modified = true;
}
}
if (new_pause != pause_item.label) {
pause_item.label = new_pause;
menu_modified = true;
}
if (new_progress != progress_item.label) {
progress_item.label = new_progress;
menu_modified = true;
}
if (menu_modified) {
if (paused) progress_item.label += ' - paused';
tray.menu = menu;
}
get_status(socket);
}, 1500);
// Menu actions contents
menu.append(new gui.MenuItem({
var local_folder_menu = new gui.MenuItem({
label: 'Open local folder',
icon: 'images/folder.png',
enabled: false,
click: function () {
var dir = globals['settings']['directory'];
console.log('Open ' + dir);
gui.Shell.showItemInFolder(dir);
}
}));
})
menu.append(local_folder_menu);
menu.append(new gui.MenuItem({
var pithos_page_menu = new gui.MenuItem({
label: 'Launch Pithos+ page',
icon: 'images/pithos.png',
enabled: false,
click: function () {
var pithos_ui = globals['settings']['pithos_ui'];
console.log('Visit ' + pithos_ui);
gui.Shell.openExternal(pithos_ui);
console.log('Visit ' + get_pithos_ui());
gui.Shell.openExternal(get_pithos_ui());
}
}));
});
menu.append(pithos_page_menu);
// Settings and About
menu.append(new gui.MenuItem({type: 'separator'}));
menu.append(new gui.MenuItem({
var settings_menu = new gui.MenuItem({
label: 'Settings',
icon: 'images/settings.png',
enabled: false,
click: function () {
export_settings(globals.settings);
if (windows['settings']) windows['settings'].close();
windows['settings'] = gui.Window.open("settings.html", {
toolbar: false, focus: true,
width: 841, height: 520,
width: 841, height: 520
});
windows['settings'].on('closed', function() {
new_settings = import_settings();
......@@ -137,7 +96,8 @@ menu.append(new gui.MenuItem({
get_settings(socket);
});
},
}));
});
menu.append(settings_menu);
menu.append(new gui.MenuItem({
label: 'About',
......@@ -159,6 +119,79 @@ menu.append(new gui.MenuItem({
click: function() {post_shutdown(socket);}
}));
// Update progress
var client_ready = false;
window.setInterval(function() {
var menu_modified = false;
if (!client_ready) {
if (!globals.authenticated) return;
client_ready = true;
}
if (client_ready) {
if (!settings_menu.enabled) {
if (globals.settings.url) refresh_endpoints(globals.settings.url);
settings_menu.enabled = true;
tray.menu = menu;
}
if (globals.settings.url && !pithos_page_menu.enabled) {
if (get_pithos_ui() != null) {
pithos_page_menu.enabled = true;
tray.menu = menu;
} else { refresh_endpoints(globals.settings.url); }
}
if (!local_folder_menu.enabled) {
if (globals.settings.directory) {
local_folder_menu.enabled = true;
tray.menu = menu;
}
}
}
var status = globals['status'];
var new_progress = progress_item.label;
var new_pause = pause_item.label;
if (status['paused'] !== null) {
switch(pause_item.label) {
case pause_syncing: if (status['paused']) {
// Update to "Paused - start syncing"
paused = true;
new_pause = start_syncing;
menu_modified = true;
} // else continue syncing
new_progress = status['progress'] + '%' + ' synced';
break;
case start_syncing: if (status['paused']) return;
// else update to "Syncing - pause syncing"
paused = false;
new_pause = pause_syncing;
new_progress = status['progress'] + '%' + ' synced';
menu_modified = true;
break;
default:
if (status['paused']) {new_pause = start_syncing; paused=true;}
else {new_pause = pause_syncing; paused=false;}
new_progress = status['progress'] + '%' + ' synced';
pause_item.enabled = true;
menu_modified = true;
}
}
if (new_pause != pause_item.label) {
pause_item.label = new_pause;
menu_modified = true;
}
if (new_progress != progress_item.label) {
progress_item.label = new_progress;
menu_modified = true;
}
if (menu_modified) {
if (paused) progress_item.label += ' - paused';
tray.menu = menu;
}
get_status(socket);
}, 1500);
tray.menu = menu;
</script>
......
......@@ -16,10 +16,10 @@ var globals = {
'url': null,
'container': null,
'directory': null,
'pithos_ui': null,
'exclude': null
},
'status': {"progress": null, "paused": null}
'status': {"progress": null, "paused": null},
'authenticated': false,
}
// Protocol: requests ::: responses
......@@ -65,12 +65,13 @@ socket.onopen = function() {
}
socket.onmessage = function(e) {
var r = JSON.parse(e.data)
//console.log('RECV: ' + r['action'])
console.log('RECV: ' + r['action'])
switch(r['action']) {
case 'post gui_id':
if (r['ACCEPTED'] === 202) {
get_settings(this);
get_status(this);
globals.authenticated = true;
} else {
console.log('Helper: ' + JSON.stringify(r));
closeWindows();
......@@ -85,7 +86,7 @@ socket.onmessage = function(e) {
}
break;
case 'get settings':
console.log(r);
// console.log(r);
globals['settings'] = r;
break;
case 'put settings':
......
......@@ -9,6 +9,7 @@
<script type="text/javascript">
var fs = require('fs');
var exclude = null;
remove_tokens = null;
$(document).ready(function() {
var url = get_setting('url');
if (url) $('#cloud-url').val(url);
......@@ -18,16 +19,11 @@
if (container) $('#container').val(container);
var directory = get_setting('directory');
if (directory) $('#directory').html(directory);
exclude = get_setting('exclude');
if (exclude) $('#exclude').val(
var exclude = get_setting('exclude');
if (exclude) try {
$('#exclude').val(
fs.readFileSync(exclude, encoding='utf-8'));
var pithos_ui = get_setting('pithos_ui');
if (pithos_ui) {
$('#get_creds').show();
} else {
console.log('No pithos view, remove credential button');
$('#get_creds').hide();
}
} catch (err) {console.log(err);}
});
function update_exclude(new_content) {
......@@ -45,30 +41,79 @@
//set_setting('token', $(this).val())
}
function remove_cookies(win, url) {
var removed_at_least_one = false
win.cookies.getAll({url: url}, function(cookies) {
$.each(cookies, function(i, cookie) {
console.log('I have a cookie to remove ' + cookie.name + ', ' + cookie.domain);
win.cookies.remove({url: url, name: cookie.name} );
removed_at_least_one = true;
});
});
return removed_at_least_one;
}
var gui = require('nw.gui');
function extract_cookie(url) {
var cred_win = null;
var logout_win = null;
var can_close_cred = false;
var got_cookie = false;
var can_close_logout = false;
var show_creds = true;
function get_credentials() {
var cookie_name = '_pithos2_a';
var logout_url = 'https://accounts.okeanos.grnet.gr/ui/logout'
var w = gui.Window.open(logout_url, {
focus: false, width: 20, height: 20
});
w.close();
var w = gui.Window.open(logout_url, {
focus: true, width: 520, height: 920
var lurl = get_account_ui() + '/logout?next=' + get_pithos_ui()
show_creds = false;
$('#get_creds').hide();
got_cookie = false;
cred_win = gui.Window.open(lurl, {
focus: true, width: 820, height: 580, toolbar: false
});
w.cookies.onChanged.addListener(function(info) {
cred_win.cookies.onChanged.addListener(function(info) {
if (info.cookie.name === cookie_name) {
console.log('Succesfully logged in');
extract_credentials(info.cookie);
w.close();
got_cookie = true;
}
});
cred_win.on('loaded', function() {
if (got_cookie) can_close_cred = true;
});
cred_win.on('closed', function() {
logout_win = gui.Window.open(
get_account_ui() + '/logout',
{focus: true, width:20, height: 20 });
logout_win.hide();
logout_win.on('loaded', function() {
while(
remove_cookies(logout_win, get_account_ui()) ||
remove_cookies(logout_win, get_pithos_ui())) {}
can_close_logout = true;
});
});
}
function get_credentials() {
var pithos_ui = get_setting('pithos_ui');
extract_cookie(pithos_ui);
window.setInterval(function() {
// Refresh get_creds visibility, until refresh_endpoints
// changes are in effect
if (get_pithos_ui() && show_creds) {
$('#get_creds').show();
} else {$('#get_creds').hide(); }
// Garbage collector
if (can_close_logout) {
can_close_logout = false;
logout_win.close();
show_creds = true;
}
else if (can_close_cred) {
can_close_cred = false;
cred_win.close();
}
}, 500);
</script>
</head>
<body>
......@@ -88,7 +133,7 @@
</div>
<div class="small-9 columns">
<input type="text" id="cloud-url" placeholder="Authentication URL"
onchange="set_setting('url', $(this).val())">
onchange="set_setting('url', $(this).val()); refresh_endpoints($(this).val());">
<small class="error" style="visibility: hidden">Invalid entry</small>
</div>
</div>
......@@ -107,7 +152,7 @@
<div class="clearfix">
<a id="get_creds"
class="button right" style="display: none;"
onclick="get_credentials();">Get credentials</a>
onclick="get_credentials();">Login to get credentials</a>
</div>
</fieldset>
......
......@@ -17,3 +17,33 @@ function get_setting(key) {
function set_setting(key, val) {
global.settings[key] = val;
}
function refresh_endpoints(identity_url) {
$.post(identity_url + '/tokens', function(data) {
var endpoints = data.access.serviceCatalog
global.pithos_ui = null;
global.account_ui = null;
$.each(endpoints, function(i, endpoint) {
switch(endpoint.type) {
case 'object-store': try {
global.pithos_ui = endpoint['endpoints'][0]['SNF:uiURL'];
} catch(err) { console.log('Failed to get pithos_ui ' + err); }
break;
case 'account': try {
global.account_ui = endpoint['endpoints'][0]['SNF:uiURL'];
} catch(err) { console.log('Failed to get account_ui ' + err); }
break;
}
});
});
}
function get_pithos_ui() {
if (global.pithos_ui) {return global.pithos_ui;}
else {return null;}
}
function get_account_ui() {
if (global.account_ui) {return global.account_ui;}
else {return null;}
}
......@@ -2,10 +2,8 @@ from ws4py.websocket import WebSocket
import json
import logging
from os.path import abspath
from titanic import syncer
from config import AgkyraConfig
from kamaki.clients.astakos import AstakosClient
from kamaki.clients.pithos import PithosClient
from agkyra.syncer import syncer
from agkyra.config import AgkyraConfig
LOG = logging.getLogger(__name__)
......@@ -72,33 +70,30 @@ class WebSocketProtocol(WebSocket):
cnf = AgkyraConfig()
def _load_settings(self):
LOG.debug('Start loading settings')
sync = self.cnf.get('global', 'default_sync')
cloud = self.cnf.get_sync(sync, 'cloud')
url = self.cnf.get_cloud(cloud, 'url')
token = self.cnf.get_cloud(cloud, 'token')
astakos = AstakosClient(url, token)
self.settings['url'], self.settings['token'] = url, token
try:
endpoints = astakos.get_endpoints()['access']['serviceCatalog']
for endpoint in endpoints:
if endpoint['type'] == PithosClient.service_type:
pithos_ui = endpoint['endpoints'][0]['SNF:uiURL']
self.settings['pithos_ui'] = pithos_ui
break
except Exception as e:
LOG.debug('Failed to retrieve pithos_ui: %s' % e)
self.settings['url'] = self.cnf.get_cloud(cloud, 'url')
except Exception:
self.settings['url'] = None
try:
self.settings['token'] = self.cnf.get_cloud(cloud, 'token')
except Exception:
self.settings['url'] = None
for option in ('container', 'directory', 'exclude'):
self.settings[option] = self.cnf.get_sync(sync, option)
LOG.debug('Finished loading settings')
def _dump_settings(self):
LOG.debug('Saving settings')
sync = self.cnf.get('global', 'default_sync')
cloud = self.cnf.get_sync(sync, 'cloud')
old_url = self.cnf.get_cloud(cloud, 'url')
old_url = self.cnf.get_cloud(cloud, 'url') or ''
while old_url != self.settings['url']:
cloud = '%s_%s' % (cloud, sync)
try:
......@@ -113,6 +108,9 @@ class WebSocketProtocol(WebSocket):
for option in ('directory', 'container', 'exclude'):
self.cnf.set_sync(sync, option, self.settings[option])
self.cnf.write()
LOG.debug('Settings saved')
# Syncer-related methods
def get_status(self):
from random import randint
......@@ -121,7 +119,6 @@ class WebSocketProtocol(WebSocket):
return self.status
def get_settings(self):
self._load_settings()
return self.settings
def set_settings(self, new_settings):
......@@ -137,7 +134,6 @@ class WebSocketProtocol(WebSocket):
# WebSocket connection methods
def opened(self):
LOG.debug('Helper: connection established')
self._load_settings()