Aidaho 2024-09-16 10:56:24 +03:00
parent 71ed2cc756
commit 85d745f7dd
20 changed files with 213 additions and 191 deletions

View File

@ -32,7 +32,7 @@ def before_request():
def register_api(view, endpoint, url, pk='listener_id', pk_type='int'): def register_api(view, endpoint, url, pk='listener_id', pk_type='int'):
view_func = view.as_view(endpoint) view_func = view.as_view(endpoint, True)
bp.add_url_rule(url, view_func=view_func, methods=['POST']) bp.add_url_rule(url, view_func=view_func, methods=['POST'])
bp.add_url_rule(f'{url}/<{pk_type}:{pk}>', view_func=view_func, methods=['GET', 'PUT', 'PATCH', 'DELETE']) bp.add_url_rule(f'{url}/<{pk_type}:{pk}>', view_func=view_func, methods=['GET', 'PUT', 'PATCH', 'DELETE'])

View File

@ -469,30 +469,6 @@ def update_db_v_4_3_0():
print("An error occurred:", e) print("An error occurred:", e)
def update_db_v_7_1_2():
try:
Setting.delete().where(Setting.param == 'stats_user').execute()
Setting.delete().where(Setting.param == 'stats_password').execute()
Setting.delete().where(Setting.param == 'stats_port').execute()
Setting.delete().where(Setting.param == 'stats_page').execute()
except Exception as e:
print("An error occurred:", e)
else:
print("Updating... DB has been updated to version 7.1.2")
def update_db_v_7_1_2_1():
try:
migrate(
migrator.add_column('cred', 'passphrase', CharField(null=True))
)
except Exception as e:
if e.args[0] == 'duplicate column name: passphrase' or str(e) == '(1060, "Duplicate column name \'passphrase\'")':
print('Updating... DB has been updated to version 7.1.2-1')
else:
print("An error occurred:", e)
def update_db_v_7_2_0(): def update_db_v_7_2_0():
try: try:
if mysql_enable: if mysql_enable:
@ -620,9 +596,26 @@ def update_db_v_8():
print("An error occurred:", e) print("An error occurred:", e)
def update_db_v_8_0_2():
try:
if mysql_enable:
migrate(
migrator.add_column('cred', 'shared', IntegerField(default=0)),
)
else:
migrate(
migrator.add_column('cred', 'shared', IntegerField(constraints=[SQL('DEFAULT 0')])),
)
except Exception as e:
if e.args[0] == 'duplicate column name: shared' or str(e) == '(1060, "Duplicate column name \'shared\'")':
print('Updating... DB has been updated to version 8.0.2')
else:
print("An error occurred:", e)
def update_ver(): def update_ver():
try: try:
Version.update(version='8.0.1').execute() Version.update(version='8.0.2').execute()
except Exception: except Exception:
print('Cannot update version') print('Cannot update version')
@ -640,12 +633,11 @@ def update_all():
if check_ver() is None: if check_ver() is None:
update_db_v_3_4_5_22() update_db_v_3_4_5_22()
update_db_v_4_3_0() update_db_v_4_3_0()
update_db_v_7_1_2()
update_db_v_7_1_2_1()
update_db_v_7_2_0() update_db_v_7_2_0()
update_db_v_7_2_0_1() update_db_v_7_2_0_1()
update_db_v_7_2_3() update_db_v_7_2_3()
update_db_v_7_3_1() update_db_v_7_3_1()
update_db_v_7_4() update_db_v_7_4()
update_db_v_8() update_db_v_8()
update_db_v_8_0_2()
update_ver() update_ver()

View File

@ -2,6 +2,8 @@ import os
import sys import sys
import traceback import traceback
from app.modules.roxywi.exception import RoxywiConflictError
def out_error(error): def out_error(error):
error = str(error) error = str(error)
@ -11,3 +13,11 @@ def out_error(error):
function_name = stk[0][2] function_name = stk[0][2]
error = f'{error} in function: {function_name} in file: {file_name}' error = f'{error} in function: {function_name} in file: {file_name}'
raise Exception(f'error: {error}') raise Exception(f'error: {error}')
def not_unique_error(error):
if error.args[0] == 1062:
col = error.args[1].split('key ')[1]
else:
col = error.args[0].split(': ')[-1]
raise RoxywiConflictError(f'{col} must be uniq')

View File

@ -4,8 +4,15 @@ from app.modules.roxywi.exception import RoxywiResourceNotFound
def select_ssh(**kwargs): def select_ssh(**kwargs):
if kwargs.get("group") and kwargs.get("cred_id"): if kwargs.get("group") and kwargs.get("cred_id") and kwargs.get("not_shared"):
query = Cred.select().where((Cred.id == kwargs.get('cred_id')) & (Cred.group_id == kwargs.get('group'))) query = Cred.select().where(
((Cred.id == kwargs.get('cred_id')) & (Cred.group_id == kwargs.get('group')))
)
elif kwargs.get("group") and kwargs.get("cred_id"):
query = Cred.select().where(
((Cred.id == kwargs.get('cred_id')) & (Cred.group_id == kwargs.get('group'))) |
(Cred.shared == 1)
)
elif kwargs.get("name") is not None: elif kwargs.get("name") is not None:
query = Cred.select().where(Cred.name == kwargs.get('name')) query = Cred.select().where(Cred.name == kwargs.get('name'))
elif kwargs.get("id") is not None: elif kwargs.get("id") is not None:
@ -13,7 +20,7 @@ def select_ssh(**kwargs):
elif kwargs.get("serv") is not None: elif kwargs.get("serv") is not None:
query = Cred.select().join(Server, on=(Cred.id == Server.cred_id)).where(Server.ip == kwargs.get('serv')) query = Cred.select().join(Server, on=(Cred.id == Server.cred_id)).where(Server.ip == kwargs.get('serv'))
elif kwargs.get("group") is not None: elif kwargs.get("group") is not None:
query = Cred.select().where(Cred.group_id == kwargs.get("group")) query = Cred.select().where((Cred.group_id == kwargs.get("group")) | (Cred.shared == 1))
else: else:
query = Cred.select() query = Cred.select()
try: try:
@ -26,11 +33,11 @@ def select_ssh(**kwargs):
return query_res return query_res
def insert_new_ssh(name, enable, group, username, password): def insert_new_ssh(name, enable, group, username, password, shared):
if password is None: if password is None:
password = 'None' password = 'None'
try: try:
return Cred.insert(name=name, key_enabled=enable, group_id=group, username=username, password=password).execute() return Cred.insert(name=name, key_enabled=enable, group_id=group, username=username, password=password, shared=shared).execute()
except Exception as e: except Exception as e:
out_error(e) out_error(e)
@ -45,21 +52,22 @@ def delete_ssh(ssh_id):
return True return True
def update_ssh(cred_id, name, enable, group, username, password): def update_ssh(cred_id: int, name: str, enable: int, group: int, username: str, password: bytes, shared: int):
if password is None: if password is None:
password = 'None' password = 'None'
cred_update = Cred.update(name=name, key_enabled=enable, group_id=group, username=username, password=password).where( cred_update = Cred.update(
Cred.id == cred_id) name=name, key_enabled=enable, group_id=group, username=username, password=password, shared=shared
).where(Cred.id == cred_id)
try: try:
cred_update.execute() cred_update.execute()
except Exception as e: except Exception as e:
out_error(e) out_error(e)
def update_ssh_passphrase(name: str, passphrase: str): def update_ssh_passphrase(cred_id: int, passphrase: str):
try: try:
Cred.update(passphrase=passphrase).where(Cred.name == name).execute() Cred.update(passphrase=passphrase).where(Cred.id == cred_id).execute()
except Exception as e: except Exception as e:
out_error(e) out_error(e)

View File

@ -196,6 +196,7 @@ class Cred(BaseModel):
password = CharField(null=True) password = CharField(null=True)
group_id = IntegerField(constraints=[SQL('DEFAULT 1')]) group_id = IntegerField(constraints=[SQL('DEFAULT 1')])
passphrase = CharField(null=True) passphrase = CharField(null=True)
shared = IntegerField(constraints=[SQL('DEFAULT 0')])
class Meta: class Meta:
table_name = 'cred' table_name = 'cred'

View File

@ -1,5 +1,7 @@
from peewee import IntegrityError
from app.modules.db.db_model import mysql_enable, connect, Server, SystemInfo from app.modules.db.db_model import mysql_enable, connect, Server, SystemInfo
from app.modules.db.common import out_error from app.modules.db.common import out_error, not_unique_error
from app.modules.roxywi.exception import RoxywiResourceNotFound from app.modules.roxywi.exception import RoxywiResourceNotFound
@ -10,9 +12,10 @@ def add_server(hostname, ip, group, type_ip, enable, master, cred, port, desc, h
port=port, description=desc, haproxy=haproxy, nginx=nginx, apache=apache, firewall_enable=firewall port=port, description=desc, haproxy=haproxy, nginx=nginx, apache=apache, firewall_enable=firewall
).execute() ).execute()
return server_id return server_id
except IntegrityError as e:
not_unique_error(e)
except Exception as e: except Exception as e:
out_error(e) out_error(e)
return False
def delete_server(server_id): def delete_server(server_id):

View File

@ -139,6 +139,7 @@ class CredRequest(BaseModel):
password: Optional[EscapedString] = None password: Optional[EscapedString] = None
key_enabled: Optional[bool] = 1 key_enabled: Optional[bool] = 1
group_id: Optional[int] = None group_id: Optional[int] = None
shared: Optional[int] = 0
class CredUploadRequest(BaseModel): class CredUploadRequest(BaseModel):

View File

@ -16,7 +16,8 @@ import app.modules.db.history as history_sql
import app.modules.db.ha_cluster as ha_sql import app.modules.db.ha_cluster as ha_sql
import app.modules.roxy_wi_tools as roxy_wi_tools import app.modules.roxy_wi_tools as roxy_wi_tools
from app.modules.roxywi.class_models import ErrorResponse from app.modules.roxywi.class_models import ErrorResponse
from app.modules.roxywi.exception import RoxywiResourceNotFound, RoxywiGroupMismatch, RoxywiGroupNotFound from app.modules.roxywi.exception import RoxywiResourceNotFound, RoxywiGroupMismatch, RoxywiGroupNotFound, \
RoxywiPermissionError
get_config_var = roxy_wi_tools.GetConfigVar() get_config_var = roxy_wi_tools.GetConfigVar()
@ -334,5 +335,7 @@ def handler_exceptions_for_json_data(ex: Exception, main_ex_mes: str) -> tuple[d
return handle_json_exceptions(ex, 'Group not found'), 404 return handle_json_exceptions(ex, 'Group not found'), 404
elif isinstance(ex, RoxywiGroupMismatch): elif isinstance(ex, RoxywiGroupMismatch):
return handle_json_exceptions(ex, 'Resource not found in group'), 404 return handle_json_exceptions(ex, 'Resource not found in group'), 404
elif isinstance(ex, RoxywiPermissionError):
return handle_json_exceptions(ex, 'You cannot edit this resource'), 403
else: else:
return handle_json_exceptions(ex, main_ex_mes), 500 return handle_json_exceptions(ex, main_ex_mes), 500

View File

@ -31,3 +31,17 @@ class RoxywiValidationError(Exception):
def __init__(self, message='Validation error'): def __init__(self, message='Validation error'):
super(RoxywiValidationError, self).__init__(message) super(RoxywiValidationError, self).__init__(message)
class RoxywiPermissionError(Exception):
""" This class represents an exception raised when a permission error occurs. """
def __init__(self, message='Forbidden'):
super(RoxywiPermissionError, self).__init__(message)
class RoxywiConflictError(Exception):
""" This class represents an exception raised when a conflict error occurs."""
def __init__(self, message='Conflict'):
super(RoxywiConflictError, self).__init__(message)

View File

@ -13,14 +13,13 @@ import app.modules.common.common as common
from app.modules.server import ssh_connection from app.modules.server import ssh_connection
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
import app.modules.roxy_wi_tools as roxy_wi_tools import app.modules.roxy_wi_tools as roxy_wi_tools
from app.modules.roxywi.class_models import IdResponse, IdDataResponse from app.modules.roxywi.class_models import IdResponse, IdDataResponse, CredRequest
error_mess = common.error_mess error_mess = common.error_mess
get_config = roxy_wi_tools.GetConfigVar() get_config = roxy_wi_tools.GetConfigVar()
def return_ssh_keys_path(server_ip: str, **kwargs) -> dict: def return_ssh_keys_path(server_ip: str, **kwargs) -> dict:
lib_path = get_config.get_config_var('main', 'lib_path')
ssh_settings = {} ssh_settings = {}
if kwargs.get('id'): if kwargs.get('id'):
sshs = cred_sql.select_ssh(id=kwargs.get('id')) sshs = cred_sql.select_ssh(id=kwargs.get('id'))
@ -43,10 +42,10 @@ def return_ssh_keys_path(server_ip: str, **kwargs) -> dict:
else: else:
passphrase = ssh.passphrase passphrase = ssh.passphrase
ssh_key = _return_correct_ssh_file(ssh)
ssh_settings.setdefault('enabled', ssh.key_enabled) ssh_settings.setdefault('enabled', ssh.key_enabled)
ssh_settings.setdefault('user', ssh.username) ssh_settings.setdefault('user', ssh.username)
ssh_settings.setdefault('password', password) ssh_settings.setdefault('password', password)
ssh_key = f'{lib_path}/keys/{ssh.name}.pem' if ssh.key_enabled == 1 else ''
ssh_settings.setdefault('key', ssh_key) ssh_settings.setdefault('key', ssh_key)
ssh_settings.setdefault('passphrase', passphrase) ssh_settings.setdefault('passphrase', passphrase)
@ -66,10 +65,8 @@ def ssh_connect(server_ip):
return ssh return ssh
def create_ssh_cred(name: str, password: str, group: int, username: str, enable: int, is_api: int) -> dict: def create_ssh_cred(name: str, password: str, group: int, username: str, enable: int, is_api: int, shared: int) -> dict:
group_name = group_sql.get_group_name_by_id(group)
lang = roxywi_common.get_user_lang_for_flask() lang = roxywi_common.get_user_lang_for_flask()
name = f'{name}_{group_name}'
if password and password != "''": if password and password != "''":
try: try:
password = crypt_password(password) password = crypt_password(password)
@ -79,43 +76,20 @@ def create_ssh_cred(name: str, password: str, group: int, username: str, enable:
password = '' password = ''
try: try:
last_id = cred_sql.insert_new_ssh(name, enable, group, username, password) last_id = cred_sql.insert_new_ssh(name, enable, group, username, password, shared)
except Exception as e: except Exception as e:
return roxywi_common.handle_json_exceptions(e, 'Cannot create new SSH credentials') return roxywi_common.handle_json_exceptions(e, 'Cannot create new SSH credentials')
roxywi_common.logging('RMON server', f'New SSH credentials {name} has been created', roxywi=1, login=1) roxywi_common.logging('Roxy-WI server', f'New SSH credentials {name} has been created', roxywi=1, login=1)
if is_api: if is_api:
return IdResponse(id=last_id).model_dump(mode='json') return IdResponse(id=last_id).model_dump(mode='json')
else: else:
data = render_template('ajax/new_ssh.html', groups=group_sql.select_groups(), sshs=cred_sql.select_ssh(name=name), lang=lang) data = render_template('ajax/new_ssh.html',
groups=group_sql.select_groups(), sshs=cred_sql.select_ssh(name=name), lang=lang, adding=1
)
return IdDataResponse(id=last_id, data=data).model_dump(mode='json') return IdDataResponse(id=last_id, data=data).model_dump(mode='json')
def create_ssh_cred_api(name: str, enable: str, group: str, username: str, password: str) -> bool:
group_name = group_sql.get_group_name_by_id(group)
name = common.checkAjaxInput(name)
name = f'{name}_{group_name}'
enable = common.checkAjaxInput(enable)
username = common.checkAjaxInput(username)
password = common.checkAjaxInput(password)
if password != '':
try:
password = crypt_password(password)
except Exception as e:
raise Exception(e)
if username is None or name is None:
return False
else:
try:
cred_sql.insert_new_ssh(name, enable, group, username, password)
roxywi_common.logging('Roxy-WI server', f'New SSH credentials {name} has been created', roxywi=1)
return True
except Exception as e:
roxywi_common.handle_exceptions(e, 'Roxy-WI server', f'Cannot create SSH credentials {name}', roxywi=1)
def upload_ssh_key(ssh_id: int, key: str, passphrase: str) -> None: def upload_ssh_key(ssh_id: int, key: str, passphrase: str) -> None:
key = key.replace("'", "") key = key.replace("'", "")
ssh = cred_sql.get_ssh(ssh_id) ssh = cred_sql.get_ssh(ssh_id)
@ -123,27 +97,14 @@ def upload_ssh_key(ssh_id: int, key: str, passphrase: str) -> None:
lib_path = get_config.get_config_var('main', 'lib_path') lib_path = get_config.get_config_var('main', 'lib_path')
full_dir = f'{lib_path}/keys/' full_dir = f'{lib_path}/keys/'
name = ssh.name name = ssh.name
ssh_keys = f'{name}.pem' ssh_keys = f'{full_dir}{name}_{group_name}.pem'
if key != '':
try: try:
key = paramiko.pkey.load_private_key(key, password=passphrase) key = paramiko.pkey.load_private_key(key, password=passphrase)
except Exception as e: except Exception as e:
raise Exception(e) raise Exception(e)
try:
_ = name.split('_')[1]
split_name = True
except Exception:
split_name = False
if not os.path.isfile(ssh_keys) and not split_name:
name = f'{ssh.name}_{group_name}'
if not os.path.exists(full_dir):
os.makedirs(full_dir)
ssh_keys = f'{full_dir}{name}.pem'
try: try:
key.write_private_key_file(ssh_keys) key.write_private_key_file(ssh_keys)
except Exception as e: except Exception as e:
@ -152,7 +113,6 @@ def upload_ssh_key(ssh_id: int, key: str, passphrase: str) -> None:
try: try:
os.chmod(ssh_keys, 0o600) os.chmod(ssh_keys, 0o600)
except IOError as e: except IOError as e:
roxywi_common.logging('RMON server', e.args[0], roxywi=1)
raise Exception(e) raise Exception(e)
if passphrase: if passphrase:
@ -168,44 +128,39 @@ def upload_ssh_key(ssh_id: int, key: str, passphrase: str) -> None:
except Exception as e: except Exception as e:
raise Exception(e) raise Exception(e)
roxywi_common.logging("RMON server", f"A new SSH cert has been uploaded {ssh_keys}", roxywi=1, login=1) roxywi_common.logging("Roxy-WI server", f"A new SSH cert has been uploaded {ssh_keys}", roxywi=1, login=1)
def update_ssh_key(ssh_id: int, name: str, password: str, enable: int, username: str, group: int) -> None: def update_ssh_key(body: CredRequest, group_id: int, ssh_id: int) -> None:
lib_path = get_config.get_config_var('main', 'lib_path')
ssh = cred_sql.get_ssh(ssh_id) ssh = cred_sql.get_ssh(ssh_id)
ssh_key_name = f'{lib_path}/keys/{ssh.name}.pem' ssh_key_name = _return_correct_ssh_file(ssh)
new_ssh_key_name = f'{lib_path}/keys/{name}.pem'
if password != '': if body.password != '' and body.password is not None:
try: try:
password = crypt_password(password) body.password = crypt_password(body.password)
except Exception as e: except Exception as e:
raise Exception(e) raise Exception(e)
if ssh.key_enabled == 1 and os.path.isfile(ssh_key_name): if os.path.isfile(ssh_key_name):
new_ssh_key_name = _return_correct_ssh_file(body)
os.rename(ssh_key_name, new_ssh_key_name) os.rename(ssh_key_name, new_ssh_key_name)
os.chmod(new_ssh_key_name, 0o600) os.chmod(new_ssh_key_name, 0o600)
try: try:
cred_sql.update_ssh(ssh_id, name, enable, group, username, password) cred_sql.update_ssh(ssh_id, body.name, body.key_enabled, group_id, body.username, body.password, body.shared)
roxywi_common.logging('RMON server', f'The SSH credentials {name} has been updated ', roxywi=1, login=1) roxywi_common.logging('Roxy-WI server', f'The SSH credentials {body.name} has been updated ', roxywi=1, login=1)
except Exception as e: except Exception as e:
raise Exception(e) raise Exception(e)
def delete_ssh_key(ssh_id) -> str: def delete_ssh_key(ssh_id) -> None:
lib_path = get_config.get_config_var('main', 'lib_path')
name = '' name = ''
ssh_enable = 0
ssh_key_name = ''
for sshs in cred_sql.select_ssh(id=ssh_id): for sshs in cred_sql.select_ssh(id=ssh_id):
ssh_enable = sshs.key_enabled
name = sshs.name name = sshs.name
ssh_key_name = f'{lib_path}/keys/{sshs.name}.pem'
if ssh_enable == 1: if sshs.key_enabled == 1:
ssh_key_name = _return_correct_ssh_file(sshs)
try: try:
os.remove(ssh_key_name) os.remove(ssh_key_name)
except Exception: except Exception:
@ -213,9 +168,8 @@ def delete_ssh_key(ssh_id) -> str:
try: try:
cred_sql.delete_ssh(ssh_id) cred_sql.delete_ssh(ssh_id)
roxywi_common.logging('Roxy-WI server', f'The SSH credentials {name} has deleted', roxywi=1, login=1) roxywi_common.logging('Roxy-WI server', f'The SSH credentials {name} has deleted', roxywi=1, login=1)
return 'ok'
except Exception as e: except Exception as e:
roxywi_common.handle_exceptions(e, 'Roxy-WI server', f'Cannot delete SSH credentials {name}', roxywi=1, login=1) raise e
def crypt_password(password: str) -> bytes: def crypt_password(password: str) -> bytes:
@ -248,12 +202,11 @@ def decrypt_password(password: str) -> str:
return decryp_pass return decryp_pass
def get_creds(group_id: int = None, cred_id: int = None) -> list: def get_creds(group_id: int = None, cred_id: int = None, not_shared: bool = False) -> list:
json_data = [] json_data = []
lib_path = get_config.get_config_var('main', 'lib_path')
if group_id and cred_id: if group_id and cred_id:
creds = cred_sql.select_ssh(group=group_id, cred_id=cred_id) creds = cred_sql.select_ssh(group=group_id, cred_id=cred_id, not_shared=not_shared)
elif group_id: elif group_id:
creds = cred_sql.select_ssh(group=group_id) creds = cred_sql.select_ssh(group=group_id)
else: else:
@ -262,15 +215,33 @@ def get_creds(group_id: int = None, cred_id: int = None) -> list:
for cred in creds: for cred in creds:
cred_dict = model_to_dict(cred) cred_dict = model_to_dict(cred)
cred_dict['name'] = cred_dict['name'].replace("'", "") cred_dict['name'] = cred_dict['name'].replace("'", "")
ssh_key_file = f'{lib_path}/keys/{cred_dict["name"]}.pem'
if cred.key_enabled == 1:
ssh_key_file = _return_correct_ssh_file(cred)
if os.path.isfile(ssh_key_file): if os.path.isfile(ssh_key_file):
with open(ssh_key_file, 'rb') as key: with open(ssh_key_file, 'rb') as key:
cred_dict['private_key'] = base64.b64encode(key.read()).decode('utf-8') cred_dict['private_key'] = base64.b64encode(key.read()).decode('utf-8')
else: else:
cred_dict['private_key'] = '' cred_dict['private_key'] = ''
else:
cred_dict['private_key'] = ''
if cred_dict['password']: if cred_dict['password']:
try:
cred_dict['password'] = decrypt_password(cred_dict['password']) cred_dict['password'] = decrypt_password(cred_dict['password'])
except Exception:
pass
if cred_dict['passphrase']: if cred_dict['passphrase']:
cred_dict['passphrase'] = decrypt_password(cred_dict['passphrase']) cred_dict['passphrase'] = decrypt_password(cred_dict['passphrase'])
json_data.append(cred_dict) json_data.append(cred_dict)
return json_data return json_data
def _return_correct_ssh_file(cred: CredRequest) -> str:
lib_path = get_config.get_config_var('main', 'lib_path')
group_name = group_sql.get_group_name_by_id(cred.group_id)
# if not cred.key_enabled:
# return ''
if group_name not in cred.name:
return f'{lib_path}/keys/{cred.name}_{group_name}.pem'
else:
return f'{lib_path}/keys/{cred.name}.pem'

View File

@ -61,6 +61,10 @@ function addCreds(dialog_id) {
if ($('#new-ssh_enable').is(':checked')) { if ($('#new-ssh_enable').is(':checked')) {
ssh_enable = '1'; ssh_enable = '1';
} }
let ssh_shared = 0;
if ($('#new-ssh_shared').is(':checked')) {
ssh_shared = '1';
}
let valid = true; let valid = true;
let allFields = $([]).add(ssh_add_div).add($('#ssh_user')) let allFields = $([]).add(ssh_add_div).add($('#ssh_user'))
allFields.removeClass("ui-state-error"); allFields.removeClass("ui-state-error");
@ -71,7 +75,8 @@ function addCreds(dialog_id) {
"name": ssh_add_div.val(), "name": ssh_add_div.val(),
"group_id": $('#new-sshgroup').val(), "group_id": $('#new-sshgroup').val(),
"username": $('#ssh_user').val(), "username": $('#ssh_user').val(),
"pass": $('#ssh_pass').val(), "password": $('#ssh_pass').val(),
"shared": ssh_shared,
"key_enabled": ssh_enable, "key_enabled": ssh_enable,
} }
$.ajax({ $.ajax({
@ -113,10 +118,14 @@ function sshKeyEnableShow(id) {
function updateSSH(id) { function updateSSH(id) {
toastr.clear(); toastr.clear();
let ssh_enable = 0; let ssh_enable = 0;
let ssh_shared = 0;
let ssh_name_val = $('#ssh_name-' + id).val(); let ssh_name_val = $('#ssh_name-' + id).val();
if ($('#ssh_enable-' + id).is(':checked')) { if ($('#ssh_enable-' + id).is(':checked')) {
ssh_enable = '1'; ssh_enable = '1';
} }
if ($('#ssh_shared-' + id).is(':checked')) {
ssh_shared = '1';
}
let group = $('#sshgroup-' + id).val(); let group = $('#sshgroup-' + id).val();
if (group === undefined || group === null) { if (group === undefined || group === null) {
group = $('#new-sshgroup').val(); group = $('#new-sshgroup').val();
@ -127,6 +136,7 @@ function updateSSH(id) {
"key_enabled": ssh_enable, "key_enabled": ssh_enable,
"username": $('#ssh_user-' + id).val(), "username": $('#ssh_user-' + id).val(),
"password": $('#ssh_pass-' + id).val(), "password": $('#ssh_pass-' + id).val(),
"shared": ssh_shared,
} }
$.ajax({ $.ajax({
url: "/server/cred/" + id, url: "/server/cred/" + id,

View File

@ -221,7 +221,7 @@ function createHaClusterStep1(edited=false, cluster_id=0, clean=true) {
toastr.error('error: Wrong VIP'); toastr.error('error: Wrong VIP');
return false; return false;
} }
jsonData = createJsonCluster('#enabled-check div div span'); let jsonData = createJsonCluster('#enabled-check div div span');
if (!validateSlaves(jsonData)) { if (!validateSlaves(jsonData)) {
return false; return false;
} }

View File

@ -1,4 +1,6 @@
{% if adding %}
{% import 'languages/'+lang|default('en')+'.html' as lang %} {% import 'languages/'+lang|default('en')+'.html' as lang %}
{% endif %}
{% for ssh in sshs %} {% for ssh in sshs %}
<tr style="width: 50%;" id="ssh-table-{{ssh.id}}" class="ssh-table-{{ssh.id}}"> <tr style="width: 50%;" id="ssh-table-{{ssh.id}}" class="ssh-table-{{ssh.id}}">
<td class="first-collumn padding10"> <td class="first-collumn padding10">
@ -24,6 +26,13 @@
</select> </select>
</td> </td>
{% endif %} {% endif %}
<td class="first-collumn" valign="top" style="padding-top: 15px;">
{% if ssh.shared == 1 %}
<label for="ssh_shared-{{ssh.id}}"></label><input type="checkbox" id="ssh_shared-{{ssh.id}}" checked>
{% else %}
<label for="ssh_shared-{{ssh.id}}"></label><input type="checkbox" id="ssh_shared-{{ssh.id}}">
{% endif %}
</td>
<td style="padding-top: 15px;"> <td style="padding-top: 15px;">
<p> <p>
<input type="text" id="ssh_user-{{ssh.id}}" class="form-control" value="{{ssh.username.replace("'", "")}}"> <input type="text" id="ssh_user-{{ssh.id}}" class="form-control" value="{{ssh.username.replace("'", "")}}">
@ -39,4 +48,16 @@
<a class="delete" onclick="confirmDeleteSsh({{ssh.id}})" title="{{lang.words.delete|title()}} {{ssh.name}}" style="cursor: pointer;"></a> <a class="delete" onclick="confirmDeleteSsh({{ssh.id}})" title="{{lang.words.delete|title()}} {{ssh.name}}" style="cursor: pointer;"></a>
</td> </td>
</tr> </tr>
{% if ssh.shared and g.user_params['group_id']|string() != ssh.group_id|string() %}
<script>
$( function() {
$('#sshgroup-{{ssh.id}}').selectmenu('disable');
$('#ssh_shared-{{ssh.id}}').checkboxradio('disable');
$('#ssh_enable-{{ssh.id}}').checkboxradio('disable');
$('#ssh_name-{{ ssh.id }}').prop('readonly', true);
$('#ssh_user-{{ ssh.id }}').prop('readonly', true);
$('#ssh_pass-{{ ssh.id }}').prop('readonly', true);
});
</script>
{% endif %}
{% endfor %} {% endfor %}

View File

@ -11,54 +11,13 @@
{% if g.user_params['role'] == 1 %} {% if g.user_params['role'] == 1 %}
<td style="width: 25%;">{{lang.words.group|title()}}</td> <td style="width: 25%;">{{lang.words.group|title()}}</td>
{% endif %} {% endif %}
<td>{{ lang.words.shared|title() }}</td>
<td style="width: 100%;" class="help_cursor" id="ssh-user-name-td"> <td style="width: 100%;" class="help_cursor" id="ssh-user-name-td">
<span title="Enter SSH user name. If SSH key disabled, enter password for SSH user">{{lang.words.username|title()}}</span> <span title="Enter SSH user name. If SSH key disabled, enter password for SSH user">{{lang.words.username|title()}}</span>
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% for ssh in sshs %} {% include 'ajax/new_ssh.html' %}
<tr style="width: 50%;" id="ssh-table-{{ssh.id}}" class="{{ loop.cycle('odd', 'even') }}">
<td class="first-collumn padding10">
{% set id = 'ssh_name-' + ssh.id|string() %}
{{ input(id, value=ssh.name.replace("'", ""), size='15') }}
</td>
<td class="first-collumn" valign="top" style="padding-top: 15px;">
{% if ssh.key_enabled == 1 %}
<label for="ssh_enable-{{ssh.id}}">{{lang.words.enable|title()}} SSH {{lang.words.key}}</label><input type="checkbox" id="ssh_enable-{{ssh.id}}" checked>
{% else %}
<label for="ssh_enable-{{ssh.id}}">{{lang.words.enable|title()}} SSH {{lang.words.key}}</label><input type="checkbox" id="ssh_enable-{{ssh.id}}">
{% endif %}
</td>
{% if g.user_params['role'] == 1 %}
<td>
<select id="sshgroup-{{ssh.id}}" name="sshgroup-{{ssh.id}}">
{% for group in groups %}
{% if ssh.group_id == group.group_id %}
<option value="{{ group.group_id }}" selected>{{ group.name }}</option>
{% else %}
<option value="{{ group.group_id }}">{{ group.name }}</option>
{% endif %}
{% endfor %}
</select>
</td>
{% endif %}
<td style="padding-top: 15px;">
<p>
{% set id = 'ssh_user-' + ssh.id|string() %}
{{ input(id, value=ssh.username, title='SSH user name') }}
</p>
{% if ssh.key_enabled == 1 %}
<input type="password" id="ssh_pass-{{ssh.id}}" class="form-control" title="User password, if SSH key is disabled" value="{{ ssh.password }}" style="display: none;" autocomplete="new-password">
{% else %}
<input type="password" id="ssh_pass-{{ssh.id}}" class="form-control" title="User password, if SSH key is disabled" value="{{ ssh.password }}" autocomplete="new-password">
{% endif %}
<br>
</td>
<td>
<a class="delete" onclick="confirmDeleteSsh({{ssh.id}})" title="{{lang.words.delete|title()}} SSH {{lang.words.creds}} {{ssh.name}}" style="cursor: pointer;"></a>
</td>
</tr>
{% endfor %}
</table> </table>
<br /><span class="add-button" title="{{lang.words.add|title()}} SSH" id="add-ssh-button">+ {{lang.words.add|title()}}</span> <br /><span class="add-button" title="{{lang.words.add|title()}} SSH" id="add-ssh-button">+ {{lang.words.add|title()}}</span>
<br /><br /> <br /><br />

View File

@ -127,6 +127,12 @@
<label for="new-ssh_enable">{{lang.words.enabled|title()}} SSH {{lang.words.key}}</label><input type="checkbox" id="new-ssh_enable" checked> <label for="new-ssh_enable">{{lang.words.enabled|title()}} SSH {{lang.words.key}}</label><input type="checkbox" id="new-ssh_enable" checked>
</td> </td>
</tr> </tr>
<tr>
<td class="padding20">{{lang.words.shared|title()}}</td>
<td>
<label for="new-ssh_shared"></label><input type="checkbox" id="new-ssh_shared">
</td>
</tr>
{% if g.user_params['role'] == 1 %} {% if g.user_params['role'] == 1 %}
<tr> <tr>
<td class="padding20">{{lang.words.group|title()}}</td> <td class="padding20">{{lang.words.group|title()}}</td>

View File

@ -952,5 +952,6 @@
"listeners": "listeners", "listeners": "listeners",
"weight": "weight", "weight": "weight",
"where": "where", "where": "where",
"shared": "shared"
} }
%} %}

View File

@ -952,5 +952,6 @@
"listeners": "les auditeurs", "listeners": "les auditeurs",
"weight": "poids", "weight": "poids",
"where": "où", "where": "où",
"shared": "commun"
} }
%} %}

View File

@ -952,5 +952,6 @@
"listeners": "ouvintes", "listeners": "ouvintes",
"weight": "peso", "weight": "peso",
"where": "onde", "where": "onde",
"shared": "partilhado"
} }
%} %}

View File

@ -952,5 +952,6 @@
"listeners": "слушатели", "listeners": "слушатели",
"weight": "вес", "weight": "вес",
"where": "где", "where": "где",
"shared": "общий"
} }
%} %}

View File

@ -9,8 +9,9 @@ import app.modules.db.cred as cred_sql
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
import app.modules.server.ssh as ssh_mod import app.modules.server.ssh as ssh_mod
from app.middleware import get_user_params, page_for_admin, check_group from app.middleware import get_user_params, page_for_admin, check_group
from app.modules.roxywi.exception import RoxywiGroupMismatch, RoxywiResourceNotFound from app.modules.db.db_model import Cred
from app.modules.roxywi.class_models import BaseResponse, GroupQuery, CredRequest, CredUploadRequest from app.modules.roxywi.exception import RoxywiGroupMismatch, RoxywiResourceNotFound, RoxywiPermissionError
from app.modules.roxywi.class_models import BaseResponse, GroupQuery, CredRequest, CredUploadRequest, ErrorResponse
from app.modules.common.common_classes import SupportClass from app.modules.common.common_classes import SupportClass
@ -65,12 +66,15 @@ class CredView(MethodView):
private_key: private_key:
type: 'string' type: 'string'
description: 'SSH private key in base64 encoded format' description: 'SSH private key in base64 encoded format'
shared:
type: 'integer'
description: 'Is shared credential for other groups or not'
404: 404:
description: 'Credential not found' description: 'Credential not found'
""" """
group_id = SupportClass.return_group_id(query) group_id = SupportClass.return_group_id(query)
try: try:
creds = ssh_mod.get_creds(group_id=group_id, cred_id=cred_id) creds = ssh_mod.get_creds(group_id=group_id, cred_id=cred_id, not_shared=True)
return jsonify(creds), 200 return jsonify(creds), 200
except Exception as e: except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get credentials') return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get credentials')
@ -109,13 +113,16 @@ class CredView(MethodView):
password: password:
type: string type: string
description: The password description: The password
shared:
type: 'integer'
description: 'Is shared credential for other groups or not'
responses: responses:
201: 201:
description: Credential addition successful description: Credential addition successful
""" """
group_id = SupportClass.return_group_id(body) group_id = SupportClass.return_group_id(body)
try: try:
return ssh_mod.create_ssh_cred(body.name, body.password, group_id, body.username, body.key_enabled, self.is_api) return ssh_mod.create_ssh_cred(body.name, body.password, group_id, body.username, body.key_enabled, self.is_api, body.shared)
except Exception as e: except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create new cred') return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create new cred')
@ -157,18 +164,24 @@ class CredView(MethodView):
password: password:
type: string type: string
description: The password description: The password
shared:
type: 'integer'
description: 'Is shared credential for other groups or not'
responses: responses:
201: 201:
description: Credential update successful description: Credential update successful
""" """
group_id = SupportClass.return_group_id(body) group_id = SupportClass.return_group_id(body)
ssh = self._get_ssh(cred_id)
if ssh.shared and g.user_params['role'] != 1 and int(group_id) != int(ssh.group_id):
return roxywi_common.handler_exceptions_for_json_data(RoxywiPermissionError(), 'You cannot change shared parameters')
try: try:
self._check_is_correct_group(cred_id) self._check_is_correct_group(cred_id)
except Exception as e: except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404 return roxywi_common.handler_exceptions_for_json_data(e, '')
try: try:
ssh_mod.update_ssh_key(cred_id, body.name, body.password, body.key_enabled, body.username, group_id) ssh_mod.update_ssh_key(body, group_id, cred_id)
return BaseResponse().model_dump(mode='json'), 201 return BaseResponse().model_dump(mode='json'), 201
except Exception as e: except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update SSH key') return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update SSH key')
@ -192,7 +205,7 @@ class CredView(MethodView):
try: try:
self._check_is_correct_group(cred_id) self._check_is_correct_group(cred_id)
except Exception as e: except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404 return roxywi_common.handler_exceptions_for_json_data(e, '')
try: try:
ssh_mod.delete_ssh_key(cred_id) ssh_mod.delete_ssh_key(cred_id)
@ -245,16 +258,19 @@ class CredView(MethodView):
except Exception as e: except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot upload SSH key') return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot upload SSH key')
@staticmethod def _check_is_correct_group(self, cred_id: int):
def _check_is_correct_group(cred_id: int):
if g.user_params['role'] == 1: if g.user_params['role'] == 1:
return True return True
ssh = self._get_ssh(cred_id)
if int(ssh.group_id) != int(g.user_params['group_id']):
raise RoxywiGroupMismatch
@staticmethod
def _get_ssh(cred_id: int) -> Cred:
try: try:
ssh = cred_sql.get_ssh(cred_id) return cred_sql.get_ssh(cred_id)
except RoxywiResourceNotFound: except RoxywiResourceNotFound:
raise RoxywiResourceNotFound raise RoxywiResourceNotFound
if ssh.group_id != g.user_params['group_id']:
raise RoxywiGroupMismatch
class CredsView(MethodView): class CredsView(MethodView):
@ -306,6 +322,9 @@ class CredsView(MethodView):
private_key: private_key:
type: 'string' type: 'string'
description: 'SSH private key in base64 encoded format' description: 'SSH private key in base64 encoded format'
shared:
type: 'integer'
description: 'Is shared credential for other groups or not'
""" """
group_id = SupportClass.return_group_id(query) group_id = SupportClass.return_group_id(query)
try: try: