Allow setting custom username for shared servers, with default as username of server being shared. #6229
parent
4450145d31
commit
fc411bfc49
docs/en_US
web
migrations/versions
pgadmin
browser/server_groups/servers
static/js
model
static/js/SchemaView
tools/import_export_servers
utils
Binary file not shown.
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 67 KiB |
|
@ -44,6 +44,9 @@ Use the fields in the *General* tab to identify the server:
|
||||||
Please note that once the server is shared, it's icon is changed in the
|
Please note that once the server is shared, it's icon is changed in the
|
||||||
object explorer.
|
object explorer.
|
||||||
|
|
||||||
|
* Use the *Shared Username* field to fill the username of the shared server
|
||||||
|
connection. By default, it will take the username of the server being shared.
|
||||||
|
|
||||||
* Provide a comment about the server in the *Comments* field.
|
* Provide a comment about the server in the *Comments* field.
|
||||||
|
|
||||||
Click the *Connection* tab to continue.
|
Click the *Connection* tab to continue.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# pgAdmin 4 - PostgreSQL Tools
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||||
|
# This software is released under the PostgreSQL Licence
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
""" Add the new column for shared server username and change
|
||||||
|
autoincrement in server table
|
||||||
|
Revision ID: 9426ad06a63b
|
||||||
|
Revises: f656e56dfdc8
|
||||||
|
Create Date: 2023-10-09 15:09:50.773035
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '9426ad06a63b'
|
||||||
|
down_revision = 'f656e56dfdc8'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# Added sqlite_autoincrement to force sqlite use auto increment instead of
|
||||||
|
# last row id for next record. For future server table changes, we need to
|
||||||
|
# add table_kwargs={'sqlite_autoincrement': True} as a param to
|
||||||
|
# batch_alter_table
|
||||||
|
with op.batch_alter_table(
|
||||||
|
"server", table_kwargs={'sqlite_autoincrement': True}) as batch_op:
|
||||||
|
batch_op.alter_column('id', autoincrement=True)
|
||||||
|
batch_op.add_column(sa.Column('shared_username', sa.String(64), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# pgAdmin only upgrades, downgrade not implemented.
|
||||||
|
pass
|
|
@ -376,7 +376,7 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||||
host=data.host,
|
host=data.host,
|
||||||
port=data.port,
|
port=data.port,
|
||||||
maintenance_db=data.maintenance_db,
|
maintenance_db=data.maintenance_db,
|
||||||
username=None,
|
username=data.shared_username,
|
||||||
save_password=0,
|
save_password=0,
|
||||||
comment=None,
|
comment=None,
|
||||||
role=data.role,
|
role=data.role,
|
||||||
|
@ -814,6 +814,7 @@ class ServerNode(PGChildNodeView):
|
||||||
'tunnel_authentication': 'tunnel_authentication',
|
'tunnel_authentication': 'tunnel_authentication',
|
||||||
'tunnel_identity_file': 'tunnel_identity_file',
|
'tunnel_identity_file': 'tunnel_identity_file',
|
||||||
'shared': 'shared',
|
'shared': 'shared',
|
||||||
|
'shared_username': 'shared_username',
|
||||||
'kerberos_conn': 'kerberos_conn',
|
'kerberos_conn': 'kerberos_conn',
|
||||||
'connection_params': 'connection_params'
|
'connection_params': 'connection_params'
|
||||||
}
|
}
|
||||||
|
@ -850,6 +851,10 @@ class ServerNode(PGChildNodeView):
|
||||||
errormsg=gettext('Not a valid Host address')
|
errormsg=gettext('Not a valid Host address')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# remove the shared username if shared is updated to False
|
||||||
|
if 'shared' in data and data['shared'] is False:
|
||||||
|
data['shared_username'] = ''
|
||||||
|
|
||||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||||
conn = manager.connection()
|
conn = manager.connection()
|
||||||
connected = conn.connected()
|
connected = conn.connected()
|
||||||
|
@ -1073,6 +1078,8 @@ class ServerNode(PGChildNodeView):
|
||||||
'port': server.port,
|
'port': server.port,
|
||||||
'db': server.maintenance_db,
|
'db': server.maintenance_db,
|
||||||
'shared': server.shared if config.SERVER_MODE else None,
|
'shared': server.shared if config.SERVER_MODE else None,
|
||||||
|
'shared_username': server.shared_username
|
||||||
|
if config.SERVER_MODE else None,
|
||||||
'username': server.username,
|
'username': server.username,
|
||||||
'gid': str(server.servergroup_id),
|
'gid': str(server.servergroup_id),
|
||||||
'group-name': sg.name if (sg and sg.name) else gettext('Servers'),
|
'group-name': sg.name if (sg and sg.name) else gettext('Servers'),
|
||||||
|
|
|
@ -36,6 +36,7 @@ export default class ServerSchema extends BaseUISchema {
|
||||||
passexec: undefined,
|
passexec: undefined,
|
||||||
passexec_expiration: undefined,
|
passexec_expiration: undefined,
|
||||||
service: undefined,
|
service: undefined,
|
||||||
|
shared_username: '',
|
||||||
use_ssh_tunnel: false,
|
use_ssh_tunnel: false,
|
||||||
tunnel_host: undefined,
|
tunnel_host: undefined,
|
||||||
tunnel_port: 22,
|
tunnel_port: 22,
|
||||||
|
@ -130,6 +131,34 @@ export default class ServerSchema extends BaseUISchema {
|
||||||
return current_user.is_admin && pgAdmin.server_mode == 'True';
|
return current_user.is_admin && pgAdmin.server_mode == 'True';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'shared_username', label: gettext('Shared Username'), type: 'text',
|
||||||
|
controlProps: { maxLength: 64},
|
||||||
|
mode: ['properties', 'create', 'edit'], deps: ['shared', 'username'],
|
||||||
|
readonly: (s)=>{
|
||||||
|
if(!this.origData.shared && s.shared) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, visible: (s)=>!obj.isShared(s),
|
||||||
|
depChange: (state, source, _topState, actionObj)=>{
|
||||||
|
let ret = {};
|
||||||
|
if(this.origData.shared) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if(source == 'username' && actionObj.oldState.username == state.shared_username) {
|
||||||
|
ret['shared_username'] = state.username;
|
||||||
|
}
|
||||||
|
if(source == 'shared') {
|
||||||
|
if(state.shared) {
|
||||||
|
ret['shared_username'] = state.username;
|
||||||
|
} else {
|
||||||
|
ret['shared_username'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
|
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
|
||||||
mode: ['properties', 'edit', 'create'],
|
mode: ['properties', 'edit', 'create'],
|
||||||
|
|
|
@ -33,7 +33,7 @@ import config
|
||||||
#
|
#
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
SCHEMA_VERSION = 35
|
SCHEMA_VERSION = 36
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
#
|
#
|
||||||
|
@ -202,42 +202,11 @@ class Server(db.Model):
|
||||||
tunnel_identity_file = db.Column(db.String(64), nullable=True)
|
tunnel_identity_file = db.Column(db.String(64), nullable=True)
|
||||||
tunnel_password = db.Column(PgAdminDbBinaryString())
|
tunnel_password = db.Column(PgAdminDbBinaryString())
|
||||||
shared = db.Column(db.Boolean(), nullable=False)
|
shared = db.Column(db.Boolean(), nullable=False)
|
||||||
|
shared_username = db.Column(db.String(64), nullable=True)
|
||||||
kerberos_conn = db.Column(db.Boolean(), nullable=False, default=0)
|
kerberos_conn = db.Column(db.Boolean(), nullable=False, default=0)
|
||||||
cloud_status = db.Column(db.Integer(), nullable=False, default=0)
|
cloud_status = db.Column(db.Integer(), nullable=False, default=0)
|
||||||
connection_params = db.Column(MutableDict.as_mutable(types.JSON))
|
connection_params = db.Column(MutableDict.as_mutable(types.JSON))
|
||||||
|
|
||||||
@property
|
|
||||||
def serialize(self):
|
|
||||||
"""Return object data in easily serializable format"""
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"user_id": self.user_id,
|
|
||||||
"servergroup_id": self.servergroup_id,
|
|
||||||
"name": self.name,
|
|
||||||
"host": self.host,
|
|
||||||
"port": self.port,
|
|
||||||
"maintenance_db": self.maintenance_db,
|
|
||||||
"username": self.username,
|
|
||||||
"password": self.password,
|
|
||||||
"save_password": self.save_password,
|
|
||||||
"role": self.role,
|
|
||||||
"comment": self.comment,
|
|
||||||
"discovery_id": self.discovery_id,
|
|
||||||
"db_res": self.db_res,
|
|
||||||
"passexec_cmd": self.passexec_cmd,
|
|
||||||
"passexec_expiration": self.passexec_expiration,
|
|
||||||
"bgcolor": self.bgcolor,
|
|
||||||
"fgcolor": self.fgcolor,
|
|
||||||
"service": self.service,
|
|
||||||
"use_ssh_tunnel": self.use_ssh_tunnel,
|
|
||||||
"tunnel_host": self.tunnel_host,
|
|
||||||
"tunnel_port": self.tunnel_port,
|
|
||||||
"tunnel_authentication": self.tunnel_authentication,
|
|
||||||
"tunnel_identity_file": self.tunnel_identity_file,
|
|
||||||
"tunnel_password": self.tunnel_password,
|
|
||||||
"connection_params": self.connection_params
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ModulePreference(db.Model):
|
class ModulePreference(db.Model):
|
||||||
"""Define a preferences table for any modules."""
|
"""Define a preferences table for any modules."""
|
||||||
|
|
|
@ -59,7 +59,8 @@ export default class DepListener {
|
||||||
if(actionObj.listener?.callback) {
|
if(actionObj.listener?.callback) {
|
||||||
state = this._getListenerData(state, actionObj.listener, actionObj);
|
state = this._getListenerData(state, actionObj.listener, actionObj);
|
||||||
} else {
|
} else {
|
||||||
let allListeners = _.filter(this._depListeners, (entry)=>_.join(currPath, '|').startsWith(_.join(entry.source, '|')));
|
// adding a extra item in path to avoid incorrect matching like shared and shared_username
|
||||||
|
let allListeners = _.filter(this._depListeners, (entry)=>_.join(currPath.concat(['']), '|').startsWith(_.join(entry.source.concat(['']), '|')));
|
||||||
if(allListeners) {
|
if(allListeners) {
|
||||||
for(const listener of allListeners) {
|
for(const listener of allListeners) {
|
||||||
state = this._getListenerData(state, listener, actionObj);
|
state = this._getListenerData(state, listener, actionObj);
|
||||||
|
|
|
@ -130,7 +130,8 @@ def load_servers():
|
||||||
data = json.loads(j.read())
|
data = json.loads(j.read())
|
||||||
|
|
||||||
# Validate the json file and data
|
# Validate the json file and data
|
||||||
errmsg = validate_json_data(data, False)
|
errmsg = validate_json_data(
|
||||||
|
data, current_user.has_role("Administrator"))
|
||||||
if errmsg is not None:
|
if errmsg is not None:
|
||||||
return internal_server_error(errmsg)
|
return internal_server_error(errmsg)
|
||||||
|
|
||||||
|
|
|
@ -493,6 +493,7 @@ def dump_database_servers(output_file, selected_servers,
|
||||||
add_value(attr_dict, "Role", server.role)
|
add_value(attr_dict, "Role", server.role)
|
||||||
add_value(attr_dict, "Comment", server.comment)
|
add_value(attr_dict, "Comment", server.comment)
|
||||||
add_value(attr_dict, "Shared", server.shared)
|
add_value(attr_dict, "Shared", server.shared)
|
||||||
|
add_value(attr_dict, "SharedUsername", server.shared_username)
|
||||||
add_value(attr_dict, "DBRestriction", server.db_res)
|
add_value(attr_dict, "DBRestriction", server.db_res)
|
||||||
add_value(attr_dict, "BGColor", server.bgcolor)
|
add_value(attr_dict, "BGColor", server.bgcolor)
|
||||||
add_value(attr_dict, "FGColor", server.fgcolor)
|
add_value(attr_dict, "FGColor", server.fgcolor)
|
||||||
|
@ -738,6 +739,8 @@ def load_database_servers(input_file, selected_servers,
|
||||||
|
|
||||||
new_server.shared = obj.get("Shared", None)
|
new_server.shared = obj.get("Shared", None)
|
||||||
|
|
||||||
|
new_server.shared_username = obj.get("SharedUsername", None)
|
||||||
|
|
||||||
new_server.kerberos_conn = obj.get("KerberosAuthentication", None)
|
new_server.kerberos_conn = obj.get("KerberosAuthentication", None)
|
||||||
|
|
||||||
# if desktop mode
|
# if desktop mode
|
||||||
|
|
Loading…
Reference in New Issue