Commit fa7d08b6 authored by Stavros Sachtouris's avatar Stavros Sachtouris

Implement user session commands for kamaki

Refs: #4340
parent 5033585e
......@@ -296,31 +296,46 @@ def _init_session(arguments, is_non_API=False):
return cloud
def init_cached_authenticator(url, tokens, config_module, logger):
def init_cached_authenticator(config_argument, cloud, logger):
try:
auth_base = None
for token in reversed(tokens):
_cnf = config_argument.value
url = _cnf.get_cloud(cloud, 'url')
tokens = _cnf.get_cloud(cloud, 'token').split()
auth_base, failed = None, []
for token in tokens:
try:
if auth_base:
auth_base.authenticate(token)
else:
auth_base = AuthCachedClient(url, token)
tmp_base = AuthCachedClient(url, token)
from kamaki.cli.commands import _command_init
fake_cmd = _command_init(dict(config=config_module))
fake_cmd = _command_init(dict(config=config_argument))
fake_cmd.client = auth_base
fake_cmd._set_log_params()
fake_cmd._update_max_threads()
auth_base.authenticate(token)
tmp_base.authenticate(token)
auth_base = tmp_base
except ClientError as ce:
if ce.status in (401, ):
logger.warning(
'WARNING: Failed to authenticate token %s' % token)
failed.append(token)
else:
raise
return auth_base
for token in failed:
r = raw_input(
'Token %s failed to authenticate. Remove it? [y/N]: ' % token)
if r in ('y', 'Y'):
tokens.remove(token)
if set(failed).difference(tokens):
_cnf.set_cloud(cloud, 'token', ' '.join(tokens))
_cnf.write()
if tokens:
return auth_base
logger.warning('WARNING: cloud.%s.token is now empty' % cloud)
except AssertionError as ae:
logger.warning('WARNING: Failed to load authenticator [%s]' % ae)
return None
return None
def _load_spec_module(spec, arguments, module):
......@@ -484,9 +499,7 @@ def run_shell(exe_string, parser, cloud):
from command_shell import _init_shell
global kloger
_cnf = parser.arguments['config']
auth_base = init_cached_authenticator(
_cnf.get_cloud(cloud, 'url'), _cnf.get_cloud(cloud, 'token').split(),
_cnf, kloger)
auth_base = init_cached_authenticator(_cnf, cloud, kloger)
try:
username, userid = (
auth_base.user_term('name'), auth_base.user_term('id'))
......
......@@ -39,7 +39,7 @@ from kamaki.clients.astakos import SynnefoAstakosClient
from kamaki.cli.commands import (
_command_init, errors, _optional_json, addLogSettings)
from kamaki.cli.command_tree import CommandTree
from kamaki.cli.errors import CLIBaseUrlError, CLISyntaxError
from kamaki.cli.errors import CLIBaseUrlError, CLISyntaxError, CLIError
from kamaki.cli.argument import (
FlagArgument, ValueArgument, IntArgument, CommaSeparatedListArgument)
from kamaki.cli.utils import format_size
......@@ -59,7 +59,7 @@ def with_temp_token(foo):
raise CLISyntaxError('A token is needed for %s' % foo)
token_bu = self.client.token
try:
self.client.token = token
self.client.token = token or token_bu
return foo(self, *args, **kwargs)
finally:
self.client.token = token_bu
......@@ -94,15 +94,15 @@ class _init_synnefo_astakosclient(_command_init):
@command(user_commands)
class user_info(_init_synnefo_astakosclient, _optional_json):
"""Authenticate a user and get info"""
class user_authenticate(_init_synnefo_astakosclient, _optional_json):
"""Authenticate a user and get all authentication information"""
@errors.generic.all
@errors.user.authenticate
@errors.user.astakosclient
@with_temp_token
def _run(self):
self._print(self.client.get_user_info(), self.print_dict)
self._print(self.client.authenticate(), self.print_dict)
def main(self, token=None):
super(self.__class__, self)._run()
......@@ -173,6 +173,164 @@ class user_quotas(_init_synnefo_astakosclient, _optional_json):
self._run()
# command user session
@command(user_commands)
class user_session(_init_synnefo_astakosclient):
"""User session commands (cached identity calls for kamaki sessions)"""
@command(user_commands)
class user_session_info(_init_synnefo_astakosclient, _optional_json):
"""Get info for (current) session user"""
arguments = dict(
uuid=ValueArgument('Query user with uuid', '--uuid'),
name=ValueArgument('Query user with username/email', '--username')
)
@errors.generic.all
@errors.user.astakosclient
def _run(self):
if self['uuid'] and self['name']:
raise CLISyntaxError(
'Arguments uuid and username are mutually exclusive',
details=['Use either uuid OR username OR none, not both'])
uuid = self['uuid'] or (self._username2uuid(self['name']) if (
self['name']) else None)
try:
token = self.auth_base.get_token(uuid) if uuid else None
except KeyError:
msg = ('id %s' % self['uuid']) if (
self['uuid']) else 'username %s' % self['name']
raise CLIError(
'No user with %s in the cached session list' % msg, details=[
'To see all cached session users',
' /user session list',
'To authenticate and add a new user in the session list',
' /user session authenticate <new token>'])
self._print(self.auth_base.user_info(token), self.print_dict)
@command(user_commands)
class user_session_authenticate(_init_synnefo_astakosclient, _optional_json):
"""Authenticate a user by token and update cache"""
@errors.generic.all
@errors.user.astakosclient
def _run(self, token=None):
ask = token and token not in self.auth_base._uuids
self._print(self.auth_base.authenticate(token), self.print_dict)
if ask and self.ask_user(
'Token is temporarily stored in memory. If it is stored in'
' kamaki configuration file, it will be available in later'
' sessions. Do you want to permanently store this token?'):
tokens = self.auth_base._uuids.keys()
tokens.remove(self.auth_base.token)
self['config'].set_cloud(
self.cloud, 'token', ' '.join([self.auth_base.token] + tokens))
self['config'].write()
def main(self, new_token=None):
super(self.__class__, self)._run()
self._run(token=new_token)
@command(user_commands)
class user_session_list(_init_synnefo_astakosclient, _optional_json):
"""List cached users"""
arguments = dict(
detail=FlagArgument('Detailed listing', ('-l', '--detail'))
)
@errors.generic.all
@errors.user.astakosclient
def _run(self):
self._print([u if self['detail'] else (dict(
id=u['id'], name=u['name'])) for u in self.auth_base.list_users()])
def main(self):
super(self.__class__, self)._run()
self._run()
@command(user_commands)
class user_session_select(_init_synnefo_astakosclient):
"""Pick a user from the cached list to be the current session user"""
@errors.generic.all
@errors.user.astakosclient
def _run(self, uuid):
try:
first_token = self.auth_base.get_token(uuid)
except KeyError:
raise CLIError(
'No user with uuid %s in the cached session list' % uuid,
details=[
'To see all cached session users',
' /user session list',
'To authenticate and add a new user in the session list',
' /user session authenticate <new token>'])
if self.auth_base.token != first_token:
self.auth_base.token = first_token
msg = 'User with id %s is now the current session user.\n' % uuid
msg += 'Do you want future sessions to also start with this user?'
if self.ask_user(msg):
tokens = self.auth_base._uuids.keys()
tokens.remove(self.auth_base.token)
tokens.insert(0, self.auth_base.token)
self['config'].set_cloud(
self.cloud, 'token', ' '.join(tokens))
self['config'].write()
self.error('User is selected for next sessions')
else:
self.error('User is not permanently selected')
else:
self.error('User was already the selected session user')
def main(self, user_uuid):
super(self.__class__, self)._run()
self._run(uuid=user_uuid)
@command(user_commands)
class user_session_remove(_init_synnefo_astakosclient):
"""Delete a user (token) from the cached list of session users"""
@errors.generic.all
@errors.user.astakosclient
def _run(self, uuid):
if uuid == self.auth_base.user_term('id'):
raise CLIError('Cannot remove current session user', details=[
'To see all cached session users',
' /user session list',
'To see current session user',
' /user session info',
'To select a different session user',
' /user session select <user uuid>'])
try:
self.auth_base.remove_user(uuid)
except KeyError:
raise CLIError('No user with uuid %s in session list' % uuid,
details=[
'To see all cached session users',
' /user session list',
'To authenticate and add a new user in the session list',
' /user session authenticate <new token>'])
if self.ask_user(
'User is removed from current session, but will be restored in'
' the next session. Remove the user from future sessions?'):
self['config'].set_cloud(
self.cloud, 'token', ' '.join(self.auth_base._uuids.keys()))
self['config'].write()
def main(self, user_uuid):
super(self.__class__, self)._run()
self._run(uuid=user_uuid)
# command admin
@command(admin_commands)
......@@ -405,14 +563,10 @@ class admin_feedback(_init_synnefo_astakosclient):
class admin_endpoints(_init_synnefo_astakosclient, _optional_json):
"""Get endpoints service endpoints"""
arguments = dict(uuid=ValueArgument('User uuid', '--uuid'))
@errors.generic.all
@errors.user.astakosclient
def _run(self):
self._print(
self.client.get_endpoints(self['uuid']),
self.print_dict)
self._print(self.client.get_endpoints(), self.print_dict)
def main(self):
super(self.__class__, self)._run()
......
......@@ -137,10 +137,10 @@ def raiseCLIError(err, message='', importance=0, details=[]):
isinstance(details, list) or isinstance(details, tuple)) else [
'%s' % details]
err_details = getattr(err, 'details', [])
if not isinstance(details, list) or isinstance(details, tuple):
details.append('%s' % err_details)
else:
if isinstance(err_details, list) or isinstance(err_details, tuple):
details += list(err_details)
else:
details.append('%s' % err_details)
origerr = (('%s' % err) or '%s' % type(err)) if err else stack[0]
message = '%s' % message or origerr
......
......@@ -99,9 +99,8 @@ def run(cloud, parser, _help):
exit(0)
cls = cmd.cmd_class
auth_base = init_cached_authenticator(
_cnf.get_cloud(cloud, 'url'), _cnf.get_cloud(cloud, 'token').split(),
_cnf, kloger) if cloud else None
auth_base = init_cached_authenticator(_cnf, cloud, kloger) if (
cloud) else None
executable = cls(parser.arguments, auth_base, cloud)
parser.update_arguments(executable.arguments)
for term in _best_match:
......
......@@ -33,13 +33,24 @@
from logging import getLogger
from astakosclient import AstakosClient as SynnefoAstakosClient
from astakosclient import AstakosClientException as SynnefoAstakosClientError
from kamaki.clients import Client, ClientError
def _astakos_error(foo):
def wrap(self, *args, **kwargs):
try:
return foo(self, *args, **kwargs)
except SynnefoAstakosClientError as sace:
self._raise_for_status(sace)
return wrap
class AstakosClient(Client):
"""Synnefo Astakos cached client wraper"""
@_astakos_error
def __init__(self, base_url, token=None):
super(AstakosClient, self).__init__(base_url, token)
self._astakos = dict()
......@@ -65,6 +76,7 @@ class AstakosClient(Client):
self._validate_token(token)
return self._astakos[self._uuids[token]]
@_astakos_error
def authenticate(self, token=None):
"""Get authentication information and store it in this client
As long as the AstakosClient instance is alive, the latest
......@@ -75,7 +87,7 @@ class AstakosClient(Client):
token = self._resolve_token(token)
astakos = SynnefoAstakosClient(
token, self.base_url, logger=getLogger('astakosclient'))
r = astakos.get_endpoints()
r = astakos.authenticate()
uuid = r['access']['user']['id']
self._uuids[token] = uuid
self._cache[uuid] = r
......@@ -84,6 +96,13 @@ class AstakosClient(Client):
self._usernames2uuids[uuid] = dict()
return self._cache[uuid]
def remove_user(self, uuid):
self._uuids.pop(self.get_token(uuid))
self._cache.pop(uuid)
self._astakos.pop(uuid)
self._uuids2usernames.pop(uuid)
self._usernames2uuids.pop(uuid)
def get_token(self, uuid):
return self._cache[uuid]['access']['token']['id']
......@@ -181,8 +200,9 @@ class AstakosClient(Client):
:returns: (dict) {uuid1: name1, uuid2: name2, ...} or oposite
"""
return self.uuids2usernames(uuids, token) if (
uuids) else self.usernnames2uuids(displaynames, token)
uuids) else self.usernames2uuids(displaynames, token)
@_astakos_error
def uuids2usernames(self, uuids, token=None):
token = self._resolve_token(token)
self._validate_token(token)
......@@ -192,6 +212,7 @@ class AstakosClient(Client):
self._uuids2usernames[uuid].update(astakos.get_usernames(uuids))
return self._uuids2usernames[uuid]
@_astakos_error
def usernames2uuids(self, usernames, token=None):
token = self._resolve_token(token)
self._validate_token(token)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment