Commit 14211322 authored by Daniele Venzano's avatar Daniele Venzano

Implement quota commands in admin commandline tool

parent b7650932
......@@ -213,6 +213,10 @@ class APIEndpoint:
if not user.role.can_change_config:
raise zoe_api.exceptions.ZoeAuthException()
user_to_del = self.user_by_id(user, user_id)
if user_to_del.username == "admin":
raise zoe_api.exceptions.ZoeRestAPIException('The admin user cannot be deleted, but it can be disabled')
self.sql.user.delete(user_id)
def user_list(self, user: zoe_lib.state.User, **filters) -> List[zoe_lib.state.User]:
......@@ -222,19 +226,22 @@ class APIEndpoint:
users = self.sql.user.select(**filters)
return users
def user_new(self, user: zoe_lib.state.User, username: str, fs_uid: int, role: str, quota: str, auth_source: str) -> int:
def user_new(self, user: zoe_lib.state.User, username: str, fs_uid: int, role_id: int, quota_id: int, auth_source: str) -> int:
"""Creates a new user."""
if not user.role.can_change_config:
raise zoe_api.exceptions.ZoeAuthException()
return self.sql.user.insert(username, fs_uid, role, quota, auth_source)
if self.role_by_id(role_id) is None:
raise zoe_api.exceptions.ZoeNotFoundException("Role {} does not exist".format(role_id))
if self.quota_by_id(quota_id) is None:
raise zoe_api.exceptions.ZoeNotFoundException("Quota {} does not exist".format(quota_id))
return self.sql.user.insert(username, fs_uid, role_id, quota_id, auth_source)
def user_update(self, user: zoe_lib.state.User, user_data):
def user_update(self, user: zoe_lib.state.User, user_id, user_data):
"""Update a user."""
if 'id' not in user_data:
raise KeyError
self.user_by_id(user, user_data['id'])
self.user_by_id(user, user_id)
update_fields = {}
......@@ -261,7 +268,7 @@ class APIEndpoint:
raise zoe_api.exceptions.ZoeRestAPIException('No role called {}'.format(user_data['role']))
update_fields['role_id'] = role.id
self.sql.user.update(user_data['id'], **update_fields)
self.sql.user.update(user_id, **update_fields)
def quota_by_name(self, quota) -> zoe_lib.state.Quota:
"""Finds a quota in the database looking it up by its name."""
......@@ -300,18 +307,23 @@ class APIEndpoint:
if not user.role.can_change_config:
raise zoe_api.exceptions.ZoeAuthException()
role = self.role_by_id(role_id)
if role.name == "default":
raise zoe_api.exceptions.ZoeRestAPIException('Cannot delete default role')
self.sql.role.delete(role_id)
def role_update(self, user: zoe_lib.state.User, role_data):
def role_update(self, user: zoe_lib.state.User, role_id: int, role_data):
"""Update a role."""
if not user.role.can_change_config:
raise zoe_api.exceptions.ZoeAuthException()
if 'id' not in role_data:
raise KeyError
self.role_by_id(role_data['id'])
role = self.role_by_id(role_id)
self.sql.role.update(role_data['id'], **role_data)
if role.name == "default" and "name" in role_data:
raise zoe_api.exceptions.ZoeRestAPIException('Cannot rename default role')
self.sql.role.update(role_id, **role_data)
def quota_new(self, user: zoe_lib.state.User, quota_data) -> int:
"""Creates a new quota."""
......@@ -333,15 +345,21 @@ class APIEndpoint:
if not user.role.can_change_config:
raise zoe_api.exceptions.ZoeAuthException()
quota = self.quota_by_id(quota_id)
if quota is None:
raise zoe_api.exceptions.ZoeNotFoundException('No such quota')
if quota.name == "default":
raise zoe_api.exceptions.ZoeRestAPIException('Cannot delete default quota')
self.sql.quota.delete(quota_id)
def quota_update(self, user: zoe_lib.state.User, quota_data):
def quota_update(self, user: zoe_lib.state.User, quota_id, quota_data):
"""Update a quota."""
if not user.role.can_change_config:
raise zoe_api.exceptions.ZoeAuthException()
if 'id' not in quota_data:
raise KeyError
self.role_by_id(quota_data['id'])
quota = self.quota_by_id(quota_id)
if quota.name == "default" and "name" in quota_data:
raise zoe_api.exceptions.ZoeRestAPIException('Cannot rename default quota')
self.sql.role.update(quota_data['id'], **quota_data)
self.sql.quota.update(quota_id, **quota_data)
......@@ -35,7 +35,7 @@ class BaseAuthenticator:
def full_auth(self, username, password) -> Union[None, User]:
"""This method verifies the username and the password against one of the external auth sources."""
user = self.state.user.select(only_one=True, **{"username": username})
if not user.enabled:
if user is None or not user.enabled:
return None
if user.auth_source == "textfile" and PlainTextAuthenticator(get_conf().auth_file).auth(username, password):
......@@ -44,6 +44,8 @@ class BaseAuthenticator:
return user
elif user.auth_source == "ldap+sasl" and LDAPAuthenticator(get_conf(), sasl=True).auth(username, password):
return user
elif user.auth_source == "internal" and user.check_password(password):
return user
else:
log.error('Unknown auth source {} for user {}, cannot authenticate'.format(user.auth_source, user.username))
return None
......@@ -23,9 +23,10 @@ class ZoeException(Exception):
def __init__(self, message='Something happened'):
super().__init__()
self.message = message
self.status_code = 500
def __str__(self):
return self.message
return '{}: {}'.format(self.status_code, self.message)
class ZoeAuthException(ZoeException):
......@@ -36,21 +37,24 @@ class ZoeAuthException(ZoeException):
self.message = 'Unauthorized'
else:
self.message = message
self.status_code = 401
class ZoeNotFoundException(ZoeException):
"""The user is looking for an object that does not exist."""
pass
def __init__(self, message=None):
super().__init__()
if message is None:
self.message = 'Not found'
else:
self.message = message
self.status_code = 404
class ZoeRestAPIException(ZoeException):
"""
An exception generated by the REST API.
"""
def __init__(self, message, status_code=400, headers=None):
def __init__(self, message, status_code=400):
super().__init__(message)
self.status_code = status_code
self.headers = headers
def __str__(self):
return '[{}] {}'.format(self.status_code, self.message)
......@@ -44,9 +44,9 @@ class ExecutionAPI(ZoeAPIRequestHandler):
success, message = self.api_endpoint.execution_terminate(self.current_user, execution_id)
if not success:
raise zoe_api.exceptions.ZoeRestAPIException(message, 400)
self.set_status(204)
self.set_status(400, message)
else:
self.set_status(204)
class ExecutionDeleteAPI(ZoeAPIRequestHandler):
......@@ -63,9 +63,9 @@ class ExecutionDeleteAPI(ZoeAPIRequestHandler):
success, message = self.api_endpoint.execution_delete(self.current_user, execution_id)
if not success:
raise zoe_api.exceptions.ZoeRestAPIException(message, 400)
self.set_status(204)
self.set_status(400, message)
else:
self.set_status(204)
class ExecutionCollectionAPI(ZoeAPIRequestHandler):
......@@ -134,7 +134,8 @@ class ExecutionCollectionAPI(ZoeAPIRequestHandler):
try:
data = tornado.escape.json_decode(self.request.body)
except ValueError:
raise zoe_api.exceptions.ZoeRestAPIException('Error decoding JSON data')
self.set_status(400, 'Error decoding JSON data')
return
application_description = data['application']
exec_name = data['name']
......
......@@ -27,7 +27,6 @@ class LoginAPI(ZoeAPIRequestHandler):
return
cookie_val = self.current_user.username
self.set_secure_cookie('zoe', cookie_val)
ret = {
......
......@@ -18,7 +18,7 @@
import tornado.escape
from zoe_api.rest_api.request_handler import ZoeAPIRequestHandler
from zoe_api.exceptions import ZoeAuthException
from zoe_api.exceptions import ZoeException
class QuotaAPI(ZoeAPIRequestHandler):
......@@ -48,12 +48,12 @@ class QuotaAPI(ZoeAPIRequestHandler):
return
try:
self.api_endpoint.quota_update(self.current_user, **data)
self.api_endpoint.quota_update(self.current_user, quota_id, data)
except KeyError:
self.set_status(400, 'Error decoding JSON data')
return
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(201)
......@@ -65,8 +65,8 @@ class QuotaAPI(ZoeAPIRequestHandler):
try:
self.api_endpoint.quota_delete(self.current_user, quota_id)
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(204)
......@@ -93,8 +93,8 @@ class QuotaCollectionAPI(ZoeAPIRequestHandler):
try:
quota = self.api_endpoint.quota_list(self.current_user, **filter_dict)
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.write(dict([(r.id, r.serialize()) for r in quota]))
......@@ -115,8 +115,8 @@ class QuotaCollectionAPI(ZoeAPIRequestHandler):
except KeyError:
self.set_status(400, 'Error decoding JSON data')
return
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(201)
......
......@@ -18,7 +18,7 @@
import logging
from zoe_api.custom_request_handler import ZoeRequestHandler
from zoe_api.exceptions import ZoeAuthException
from zoe_api.exceptions import ZoeException
log = logging.getLogger(__name__)
......@@ -50,7 +50,7 @@ class ZoeAPIRequestHandler(ZoeRequestHandler):
"""In case auth fails, redirect to login page."""
try:
user = super().get_current_user()
except ZoeAuthException as e:
self.set_status(401, "Unauthorized access: {}".format(e))
except ZoeException as e:
self.set_status(e.status_code, "Unauthorized access: {}".format(e))
return None
return user
......@@ -18,7 +18,7 @@
import tornado.escape
from zoe_api.rest_api.request_handler import ZoeAPIRequestHandler
from zoe_api.exceptions import ZoeAuthException
from zoe_api.exceptions import ZoeException
class RoleAPI(ZoeAPIRequestHandler):
......@@ -48,12 +48,12 @@ class RoleAPI(ZoeAPIRequestHandler):
return
try:
self.api_endpoint.role_update(self.current_user, **data)
self.api_endpoint.role_update(self.current_user, role_id, data)
except KeyError:
self.set_status(400, 'Error decoding JSON data')
return
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(201)
......@@ -65,8 +65,8 @@ class RoleAPI(ZoeAPIRequestHandler):
try:
self.api_endpoint.role_delete(self.current_user, role_id)
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(204)
......@@ -93,8 +93,8 @@ class RoleCollectionAPI(ZoeAPIRequestHandler):
try:
role = self.api_endpoint.role_list(self.current_user, **filter_dict)
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.write(dict([(r.id, r.serialize()) for r in role]))
......@@ -115,8 +115,8 @@ class RoleCollectionAPI(ZoeAPIRequestHandler):
except KeyError:
self.set_status(400, 'Error decoding JSON data')
return
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(201)
......
......@@ -18,7 +18,7 @@
import tornado.escape
from zoe_api.rest_api.request_handler import ZoeAPIRequestHandler
from zoe_api.exceptions import ZoeAuthException, ZoeRestAPIException
from zoe_api.exceptions import ZoeException
class UserAPI(ZoeAPIRequestHandler):
......@@ -35,6 +35,9 @@ class UserAPI(ZoeAPIRequestHandler):
}
else:
user = self.api_endpoint.user_by_id(self.current_user, user_id)
if user is None:
self.set_status(404, "No such user")
return
ret = {
'user': user.serialize()
}
......@@ -53,12 +56,12 @@ class UserAPI(ZoeAPIRequestHandler):
return
try:
self.api_endpoint.user_update(self.current_user, data)
self.api_endpoint.user_update(self.current_user, user_id, data)
except KeyError:
self.set_status(400, 'Error decoding JSON data')
return
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(201)
......@@ -70,8 +73,8 @@ class UserAPI(ZoeAPIRequestHandler):
try:
self.api_endpoint.user_delete(self.current_user, user_id)
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(204)
......@@ -104,8 +107,8 @@ class UserCollectionAPI(ZoeAPIRequestHandler):
try:
users = self.api_endpoint.user_list(self.current_user, **filter_dict)
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.write(dict([(u.id, u.serialize()) for u in users]))
......@@ -122,12 +125,12 @@ class UserCollectionAPI(ZoeAPIRequestHandler):
return
try:
new_id = self.api_endpoint.user_new(self.current_user, data['username'], data['fs_uid'], data['role'], data['quota'], data['auth_source'])
new_id = self.api_endpoint.user_new(self.current_user, data['username'], data['fs_uid'], data['role_id'], data['quota_id'], data['auth_source'])
except KeyError:
self.set_status(400, 'Error decoding JSON data')
return
except ZoeAuthException as e:
self.set_status(401, e.message)
except ZoeException as e:
self.set_status(e.status_code, e.message)
return
self.set_status(201)
......
......@@ -29,7 +29,8 @@ class ZAppValidateAPI(ZoeAPIRequestHandler):
try:
data = tornado.escape.json_decode(self.request.body)
except ValueError:
raise zoe_api.exceptions.ZoeRestAPIException('Error decoding JSON data')
self.set_status(400, 'Error decoding JSON data')
return
application_description = data['application']
......
# Copyright (c) 2016, Daniele Venzano
# Copyright (c) 2018, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......@@ -25,6 +25,9 @@ from .validation import ZoeValidationAPI
from .executions import ZoeExecutionsAPI
from .services import ZoeServiceAPI
from .statistics import ZoeStatisticsAPI
from .user import ZoeUserAPI
from .role import ZoeRoleAPI
from .quota import ZoeQuotaAPI
class ZoeAPI:
......@@ -37,6 +40,9 @@ class ZoeAPI:
self.executions = ZoeExecutionsAPI(url, self.token)
self.services = ZoeServiceAPI(url, self.token)
self.statistics = ZoeStatisticsAPI(url, self.token)
self.user = ZoeUserAPI(url, self.token)
self.role = ZoeRoleAPI(url, self.token)
self.quota = ZoeQuotaAPI(url, self.token)
self._check_api_version()
def _check_api_version(self):
......@@ -61,4 +67,7 @@ class ZoeAPI:
except requests.exceptions.ConnectionError as e:
raise ZoeAPIException('Connection error: {}'.format(e))
return req.headers['Set-Cookie'], req.json()['user']
if req.status_code == 200:
return req.headers['Set-Cookie'], req.json()['user']
else:
raise ZoeAPIException('Authentication error: {}'.format(req.reason))
......@@ -108,10 +108,10 @@ class ZoeAPIBase:
except requests.exceptions.ConnectionError as e:
raise ZoeAPIException('Connection error: {}'.format(e))
try:
if not req.ok:
data = req.reason
else:
data = req.json()
except ValueError:
data = None
return data, req.status_code
@retry(ZoeAPIException)
......@@ -130,10 +130,13 @@ class ZoeAPIBase:
except requests.exceptions.ConnectionError as e:
raise ZoeAPIException('Connection error: {}'.format(e))
try:
data = req.json()
except ValueError:
data = None
if not req.ok:
data = req.reason
else:
try:
data = req.json()
except ValueError:
data = None
return data, req.status_code
@retry(ZoeAPIException)
......@@ -152,8 +155,8 @@ class ZoeAPIBase:
except requests.exceptions.ConnectionError as e:
raise ZoeAPIException('Connection error: {}'.format(e))
try:
data = req.json()
except ValueError:
if not req.ok:
data = req.reason
else:
data = None
return data, req.status_code
......@@ -43,7 +43,7 @@ class ZoeExecutionsAPI(ZoeAPIBase):
if status_code == 204:
return
else:
raise ZoeAPIException(data['message'])
raise ZoeAPIException(data)
def delete(self, execution_id):
"""
......@@ -59,7 +59,7 @@ class ZoeExecutionsAPI(ZoeAPIBase):
if status_code == 204:
return
else:
raise ZoeAPIException(data['message'])
raise ZoeAPIException(data)
def list(self, **kwargs):
"""
......@@ -84,7 +84,7 @@ class ZoeExecutionsAPI(ZoeAPIBase):
if status_code == 200:
return data
else:
raise ZoeAPIException(data['message'])
raise ZoeAPIException(data)
def get(self, execution_id):
"""
......@@ -99,8 +99,10 @@ class ZoeExecutionsAPI(ZoeAPIBase):
data, status_code = self._rest_get('/execution/' + str(execution_id))
if status_code == 200:
return data
else:
elif status_code == 404:
return None
else:
raise ZoeAPIException(data)
def start(self, name, application_description):
"""
......@@ -120,7 +122,7 @@ class ZoeExecutionsAPI(ZoeAPIBase):
}
data, status_code = self._rest_post('/execution', execution)
if status_code != 201:
raise ZoeAPIException(data['message'])
raise ZoeAPIException(data)
else:
return data['execution_id']
......
......@@ -37,8 +37,6 @@ class ZoeInfoAPI(ZoeAPIBase):
"""
data, status_code = self._rest_get('/info')
if status_code != 200:
if status_code == 404:
raise ZoeAPIException('API endpoint not found')
raise ZoeAPIException(data['message'])
raise ZoeAPIException(data)
else:
return data
# Copyright (c) 2018, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module contains all quota-related API calls that a Zoe client can use.
"""
import logging
from zoe_cmd.api_lib.api_base import ZoeAPIBase
from zoe_lib.exceptions import ZoeAPIException
log = logging.getLogger(__name__)
class ZoeQuotaAPI(ZoeAPIBase):
"""
The quota API class.
"""
def get(self, quota_id: int) -> dict:
"""
Retrieve a quota by its ID.
:param quota_id: the service to query
:return:
:type quota_id: int
:rtype: dict
"""
data, status_code = self._rest_get('/quota/' + str(quota_id))
if status_code == 200:
return data['quota']
elif status_code == 404:
raise ZoeAPIException('quota "{}" not found'.format(quota_id))
else:
raise ZoeAPIException('error retrieving quota {}: {}'.format(quota_id, data))
def delete(self, quota_id: int) -> None:
"""
Delete a quota.
:param quota_id:
:return:
:type quota_id: int
:rtype: dict
"""
data, status_code = self._rest_delete('/quota/{}'.format(quota_id))
if status_code != 204:
raise ZoeAPIException(data)
def create(self, quota: dict):
"""
Create a quota.
:param quota:
:return:
"""
data, status_code = self._rest_post('/quota', quota)
if status_code != 201:
raise ZoeAPIException(data)
return data['quota_id']
def list(self, filters: dict):
"""
List quotas, with an optional filter.
:param filters: a dictionary with zero or more keys: quotaname, email, priority, enables, auth_source, quota_id, quota_id
:return:
"""
data, status_code = self._rest_get('/quota', filters)
if status_code != 200:
raise ZoeAPIException(data)
return data
def update(self, quota_id: int, entries: dict) -> None:
"""
Update a quota.
:param quota_id:
:param entries:
:return:
"""
data, status_code = self._rest_post('/quota/{}'.format(quota_id), entries)
if status_code != 201:
raise ZoeAPIException(data)
# Copyright (c) 2018, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module contains all role-related API calls that a Zoe client can use.
"""
import logging
from zoe_cmd.api_lib.api_base import ZoeAPIBase
from zoe_lib.exceptions import ZoeAPIException
log = logging.getLogger(__name__)
class ZoeRoleAPI(ZoeAPIBase):
"""
The role API class.
"""
def get(self, role_id: int) -> dict:
"""
Retrieve a role by its ID.
:param role_id: the service to query
:return:
:type role_id: int
:rtype: dict
"""
data, status_code = self._rest_get('/role/' + str(role_id))
if status_code == 200:
return data['role']
elif status_code == 404:
raise ZoeAPIException('role "{}" not found'.format(role_id))
else:
raise ZoeAPIException('error retrieving role {}: {}'.format(role_id, data))
def delete(self, role_id: int) -> None:
"""
Delete a role.
:param role_id:
:return:
:type role_id: int
:rtype: dict
"""
data, status_code = self._rest_delete('/role/{}'.format(role_id))
if status_code != 204:
raise ZoeAPIException(data)
def create(self, role: dict):