Commit 8be21d18 authored by Daniele Venzano's avatar Daniele Venzano

Start implementing user commands

parent aaaca527
......@@ -226,7 +226,7 @@ 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_id: int, quota_id: int, auth_source: str) -> int:
def user_new(self, user: zoe_lib.state.User, username: str, email: str, 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()
......@@ -236,7 +236,7 @@ class APIEndpoint:
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)
return self.sql.user.insert(username, email, auth_source, role_id, quota_id)
def user_update(self, user: zoe_lib.state.User, user_id, user_data):
"""Update a user."""
......@@ -257,16 +257,21 @@ class APIEndpoint:
update_fields['enabled'] = user_data['enabled']
if 'auth_source' in user_data:
update_fields['auth_source'] = user_data['auth_source']
if 'quota' in user_data:
quota = self.quota_by_name(user_data['quota'])
if user_data['auth_source'] != 'internal':
user_data['password'] = None
if 'quota_id' in user_data:
quota = self.quota_by_id(user_data['quota_id'])
if quota is None:
raise zoe_api.exceptions.ZoeRestAPIException('No quota called {}'.format(user_data['quota']))
raise zoe_api.exceptions.ZoeRestAPIException('No quota with ID {}'.format(user_data['quota_id']))
update_fields['quota_id'] = quota.id
if 'role' in user_data:
role = self.role_by_name(user_data['role'])
role = self.role_by_id(user_data['role_id'])
if role is None:
raise zoe_api.exceptions.ZoeRestAPIException('No role called {}'.format(user_data['role']))
raise zoe_api.exceptions.ZoeRestAPIException('No role with ID {}'.format(user_data['role_id']))
update_fields['role_id'] = role.id
if 'password' in user_data:
update_fields['password'] = user_data['password']
update_fields['auth_source'] = 'internal'
self.sql.user.update(user_id, **update_fields)
......
......@@ -53,10 +53,6 @@ def zoe_web_main(test_conf=None) -> int:
logging.getLogger("MARKDOWN").setLevel(logging.WARNING)
logging.getLogger("tornado").setLevel(logging.WARNING)
if config.get_conf().auth_type == 'ldap' and not zoe_api.auth.ldap.LDAP_AVAILABLE:
log.error("LDAP authentication requested, but 'pyldap' module not installed.")
return 1
sql_manager = zoe_lib.state.SQLManager(config.get_conf())
sql_manager.init_db()
......
......@@ -102,6 +102,12 @@ class UserCollectionAPI(ZoeAPIRequestHandler):
if filt[0] in self.request.arguments:
if filt[1] == str:
filter_dict[filt[0]] = self.request.arguments[filt[0]][0].decode('utf-8')
if filt[1] == bool:
text_val = self.request.arguments[filt[0]][0].decode('utf-8')
if text_val == 'False':
filter_dict[filt[0]] = False
elif text_val == 'True':
filter_dict[filt[0]] = True
else:
filter_dict[filt[0]] = filt[1](self.request.arguments[filt[0]][0])
......@@ -125,7 +131,7 @@ class UserCollectionAPI(ZoeAPIRequestHandler):
return
try:
new_id = self.api_endpoint.user_new(self.current_user, data['username'], data['fs_uid'], data['role_id'], data['quota_id'], data['auth_source'])
new_id = self.api_endpoint.user_new(self.current_user, data['username'], data['email'], data['role_id'], data['quota_id'], data['auth_source'])
except KeyError:
self.set_status(400, 'Error decoding JSON data')
return
......
......@@ -82,7 +82,7 @@ class ZoeExecutionsAPI(ZoeAPIBase):
"""
data, status_code = self._rest_get('/execution', kwargs)
if status_code == 200:
return data
return list(data.values())
else:
raise ZoeAPIException(data)
......
......@@ -82,7 +82,7 @@ class ZoeQuotaAPI(ZoeAPIBase):
data, status_code = self._rest_get('/quota', filters)
if status_code != 200:
raise ZoeAPIException(data)
return data
return list(data.values())
def update(self, quota_id: int, entries: dict) -> None:
"""
......
......@@ -82,7 +82,7 @@ class ZoeRoleAPI(ZoeAPIBase):
data, status_code = self._rest_get('/role', filters)
if status_code != 200:
raise ZoeAPIException(data)
return data
return list(data.values())
def update(self, role_id: int, entries: dict) -> None:
"""
......
......@@ -82,7 +82,7 @@ class ZoeUserAPI(ZoeAPIBase):
data, status_code = self._rest_get('/user', filters)
if status_code != 200:
raise ZoeAPIException(data)
return data
return list(data.values())
def update(self, user_id: int, entries) -> None:
"""
......
......@@ -87,7 +87,7 @@ def exec_list_cmd(api: ZoeAPI, args):
data = api.executions.list(**filters)
if len(data) == 0:
return
tabular_data = [[e['id'], e['name'], e['user_id'], e['status']] for e in sorted(data.values(), key=lambda x: x['id'])]
tabular_data = [[e['id'], e['name'], e['user_id'], e['status']] for e in sorted(data, key=lambda x: x['id'])]
headers = ['ID', 'Name', 'User ID', 'Status']
print(tabulate(tabular_data, headers))
......
......@@ -75,7 +75,7 @@ def exec_list_cmd(api: ZoeAPI, args):
data = api.executions.list(**filters)
if len(data) == 0:
return
tabular_data = [[e['id'], e['name'], e['user_id'], e['status']] for e in sorted(data.values(), key=lambda x: x['id'])]
tabular_data = [[e['id'], e['name'], e['user_id'], e['status']] for e in sorted(data, key=lambda x: x['id'])]
headers = ['ID', 'Name', 'User ID', 'Status']
print(tabulate(tabular_data, headers))
......@@ -147,7 +147,7 @@ def quota_ls_cmd(api: ZoeAPI, args):
if 'name' in args:
filters['name'] = args.name
quotas = api.quota.list(filters)
tabular_data = [[q['id'], q['name'], q['concurrent_executions'], q['memory'], q['cores']] for q_id, q in sorted(quotas.items())]
tabular_data = [[q['id'], q['name'], q['concurrent_executions'], q['memory'], q['cores']] for q in sorted(quotas)]
headers = ['ID', 'Name', 'Conc. Executions', 'Memory', 'Cores']
print(tabulate(tabular_data, headers))
......@@ -201,10 +201,10 @@ def role_ls_cmd(api: ZoeAPI, args):
return "No"
filters = {}
if 'name' in args:
if args.name is not None:
filters['name'] = args.name
roles = api.role.list(filters)
tabular_data = [[r['id'], r['name'], b2t(r['can_see_status']), b2t(r['can_change_config']), b2t(r['can_operate_others']), b2t(r['can_delete_executions']), b2t(r['can_access_api']), b2t(r['can_customize_resources'])] for r_id, r in sorted(roles.items())]
tabular_data = [[r['id'], r['name'], b2t(r['can_see_status']), b2t(r['can_change_config']), b2t(r['can_operate_others']), b2t(r['can_delete_executions']), b2t(r['can_access_api']), b2t(r['can_customize_resources'])] for r in sorted(roles)]
headers = ['ID', 'Name', 'See status', 'Change config', 'Operate others', 'Delete execs', 'API access', 'Customize resources']
print(tabulate(tabular_data, headers))
......@@ -265,6 +265,92 @@ def role_update_cmd(api: ZoeAPI, args):
api.role.update(args.id, role_update)
def user_ls_cmd(api: ZoeAPI, args):
"""List defined users."""
filters = {}
if args.username is not None:
filters['username'] = args.username
if args.enabled is not None:
filters['enabled'] = True if args.enabled == 1 else False
if args.auth_source is not None:
filters['auth_source'] = args.auth_source
if args.role is not None:
role = api.role.list({'name': args.role})[0]
if role is None:
print('Unknown role specified')
return
filters['role_id'] = role['id']
if args.quota is not None:
quota = api.quota.list({'name': args.quota})[0]
if quota is None:
print('Unknown quota specified')
return
filters['quota_id'] = quota['id']
users = api.user.list(filters)
tabular_data = []
role_cache = {}
quota_cache = {}
for user in sorted(users, key=lambda u: u['id']):
if user['role_id'] in role_cache:
role = role_cache[user['role_id']]
else:
role = api.role.get(user['role_id'])
role_cache[user['role_id']] = role
if user['quota_id'] in quota_cache:
quota = quota_cache[user['quota_id']]
else:
quota = api.quota.get(user['quota_id'])
quota_cache[user['quota_id']] = quota
tabular_data.append([user['id'], user['username'], user['email'], user['fs_uid'], user['priority'], user['enabled'], user['auth_source'], role['name'], quota['name']])
headers = ['ID', 'Username', 'Email', 'FS UID', 'Priority', 'Enabled', 'Auth source', 'Role', 'Quota']
print(tabulate(tabular_data, headers))
def user_get_cmd(api: ZoeAPI, args):
"""Get a user by its ID."""
user = api.user.get(args.id)
role = api.role.get(user['role_id'])
quota = api.quota.get(role['quota_id'])
tabular_data = [[user['id'], user['username'], user['email'], user['fs_uid'], user['priority'], user['enabled'], user['auth_source'], role['name'], quota['name']]]
headers = ['ID', 'Username', 'Email', 'FS UID', 'Priority', 'Enabled', 'Auth source', 'Role', 'Quota']
print(tabulate(tabular_data, headers))
def user_create_cmd(api: ZoeAPI, args):
"""Creates a user."""
user = {
'username': args.username,
'email': args.email,
'auth_source': args.auth_source,
}
quota = api.quota.list({'name': args.quota})[0]
if quota is None:
print('Unknown quota')
return
user['quota_id'] = quota['id']
role = api.role.list({'name': args.role})[0]
if role is None:
print('Unknown role')
return
user['role_id'] = role['id']
new_id = api.user.create(user)
print('New user created with ID: {}'.format(new_id))
def user_delete_cmd(api: ZoeAPI, args):
"""Delete a user."""
api.user.delete(args.id)
def user_update_cmd(api: ZoeAPI, args):
"""Updates a user."""
pass
ENV_HELP_TEXT = '''To authenticate with Zoe you need to define three environment variables:
ZOE_URL: point to the URL of the Zoe Scheduler (ex.: http://localhost:5000/
ZOE_USER: the username used for authentication
......@@ -380,6 +466,43 @@ def process_arguments() -> Tuple[ArgumentParser, Namespace]:
sub_parser.add_argument('--can_customize_resources', choices=[0, 1], type=int, help="Can customize resource reservations before starting executions")
sub_parser.set_defaults(func=role_update_cmd)
# Users
sub_parser = subparser.add_parser('user-ls', help="List existing users")
sub_parser.add_argument('--username', help="Filter by user name")
sub_parser.add_argument('--enabled', type=int, choices=[0, 1], help="Filter by enabled status")
sub_parser.add_argument('--auth_source', choices=['internal', 'ldap', 'ldap+ssl', 'textfile'], help="Filter by auth source")
sub_parser.add_argument('--role', help="Filter by role name")
sub_parser.add_argument('--quota', help="Filter by quota name")
sub_parser.set_defaults(func=user_ls_cmd)
sub_parser = subparser.add_parser('user-get', help="Get a user by its ID")
sub_parser.add_argument('id', type=int, help="User ID")
sub_parser.set_defaults(func=user_get_cmd)
sub_parser = subparser.add_parser('user-create', help="Create a new user")
sub_parser.add_argument('username', help="Username")
sub_parser.add_argument('email', help="Email")
sub_parser.add_argument('auth_source', choices=['internal', 'ldap', 'ldap+ssl', 'textfile'], help="Authentication method")
sub_parser.add_argument('role', help="Role name")
sub_parser.add_argument('quota', help="Quota name")
sub_parser.set_defaults(func=user_create_cmd)
sub_parser = subparser.add_parser('user-delete', help="Delete a user")
sub_parser.add_argument('id', type=int, help="User ID")
sub_parser.set_defaults(func=user_delete_cmd)
sub_parser = subparser.add_parser('user-update', help="Update an existing role")
sub_parser.add_argument('id', type=int, help="ID of the user to update")
sub_parser.add_argument('--email', help="Change the email")
sub_parser.add_argument('--fs_uid', type=int, help="Filesystem UID")
sub_parser.add_argument('--password', help="Change or set the password for internal authentication")
sub_parser.add_argument('--enabled', type=int, choices=[0, 1], help="Enable or disable the user")
sub_parser.add_argument('--auth_source', choices=['internal', 'ldap', 'ldap+ssl', 'textfile'], help="Change the authentication source")
sub_parser.add_argument('--priority', type=int, help="Change priority")
sub_parser.add_argument('--role_id', help="Change role")
sub_parser.add_argument('--quota_id', help="Change quota")
sub_parser.set_defaults(func=user_update_cmd)
return parser, parser.parse_args()
......
......@@ -79,9 +79,6 @@ def load_configuration(test_conf=None):
argparser.add_argument('--master-url', help='URL of the Zoe master process', default='tcp://127.0.0.1:4850')
argparser.add_argument('--cookie-secret', help='secret used to encrypt cookies', default='changeme')
# API auth options
argparser.add_argument('--auth-type', help='Authentication type (text, ldap or ldapsasl)', default='text')
argparser.add_argument('--auth-file', help='Path to the CSV file containing user,pass,role lines for text authentication', default='zoepass.csv')
argparser.add_argument('--ldap-server-uri', help='LDAP server to use for authentication', default='ldap://localhost')
......
......@@ -158,9 +158,9 @@ class UserTable(BaseTable):
else:
return [User(x, self.sql_manager) for x in self.cursor]
def insert(self, username, fs_uid, role_id, quota_id, auth_source):
def insert(self, username: str, email: str, auth_source: str, role_id: int, quota_id: int):
"""Adds a new user to the state."""
query = self.cursor.mogrify('INSERT INTO "user" (id, username, fs_uid, email, priority, enabled, auth_source, role_id, quota_id) VALUES (DEFAULT, %s, %s, NULL, DEFAULT, DEFAULT, %s, %s, %s) RETURNING id', (username, fs_uid, auth_source, role_id, quota_id))
query = self.cursor.mogrify('INSERT INTO "user" (id, username, fs_uid, email, priority, enabled, auth_source, role_id, quota_id) VALUES (DEFAULT, %s, (SELECT MAX("user".fs_uid)+1 FROM "user"), %s, DEFAULT, TRUE, %s, %s, %s) RETURNING id', (username, email, auth_source, role_id, quota_id))
self.cursor.execute(query)
self.sql_manager.commit()
return self.cursor.fetchone()[0]
......
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