1) Added support for setting PostgreSQL connection parameters. #4728

2) Fixed an issue where Kerberos authentication to the server is not imported/exported. #5732
3) Increase the length of the value column of the setting table. #5746
4) Upgrade Flask-Migrate to 4.0.0. #5525
This commit is contained in:
Akshay Joshi
2023-01-23 17:19:59 +05:30
committed by GitHub
parent 91049445dd
commit a7cf698d09
31 changed files with 594 additions and 616 deletions

View File

@@ -29,6 +29,7 @@ define('pgadmin.node.server', [
type: 'server',
dialogHelp: url_for('help.static', {'filename': 'server_dialog.html'}),
label: gettext('Server'),
width: pgBrowser.stdW.md + 'px',
canDrop: function(node){
let serverOwner = node.user_id;
return !(serverOwner != current_user.id && !_.isUndefined(serverOwner));

View File

@@ -9,15 +9,12 @@
import gettext from 'sources/gettext';
import _ from 'lodash';
import {Address4, Address6} from 'ip-address';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import pgAdmin from 'sources/pgadmin';
import {default as supportedServers} from 'pgadmin.server.supported_servers';
import current_user from 'pgadmin.user_management.current_user';
import { isEmptyString } from 'sources/validators';
import VariableSchema from './variable.ui';
export default class ServerSchema extends BaseUISchema {
constructor(serverGroupOptions=[], userId=0, initValues={}) {
@@ -27,9 +24,7 @@ export default class ServerSchema extends BaseUISchema {
name: '',
bgcolor: '',
fgcolor: '',
sslmode: 'prefer',
host: '',
hostaddr: '',
port: 5432,
db: 'postgres',
username: current_user.name,
@@ -38,14 +33,8 @@ export default class ServerSchema extends BaseUISchema {
password: undefined,
save_password: false,
db_res: [],
passfile: undefined,
passexec: undefined,
passexec_expiration: undefined,
sslcompression: false,
sslcert: undefined,
sslkey: undefined,
sslrootcert: undefined,
sslcrl: undefined,
service: undefined,
use_ssh_tunnel: 0,
tunnel_host: undefined,
@@ -55,16 +44,22 @@ export default class ServerSchema extends BaseUISchema {
tunnel_password: undefined,
tunnel_authentication: false,
save_tunnel_password: false,
connect_timeout: 10,
connection_string: undefined,
connection_params: [
{'name': 'sslmode', 'value': 'prefer', 'keyword': 'sslmode'},
{'name': 'connect_timeout', 'value': 10, 'keyword': 'connect_timeout'}],
...initValues,
});
this.serverGroupOptions = serverGroupOptions;
this.paramSchema = new VariableSchema(this.getConnectionParameters(), null, null, ['name', 'keyword', 'value']);
this.userId = userId;
_.bindAll(this, 'isShared', 'isSSL');
_.bindAll(this, 'isShared');
}
get SSL_MODES() { return ['prefer', 'require', 'verify-ca', 'verify-full']; }
initialise(state) {
this.paramSchema.setAllReadOnly(this.isConnected(state));
}
isShared(state) {
return !this.isNew(state) && this.userId != current_user.id && state.shared;
@@ -74,16 +69,6 @@ export default class ServerSchema extends BaseUISchema {
return Boolean(state.connected);
}
isSSL(state) {
return this.SSL_MODES.indexOf(state.sslmode) == -1;
}
isValidLib() {
// older version of libpq do not support 'passfile' parameter in
// connect method, valid libpq must have version >= 100000
return pgAdmin.Browser.utils.pg_libpq_version < 100000;
}
get baseFields() {
let obj = this;
return [
@@ -148,8 +133,10 @@ export default class ServerSchema extends BaseUISchema {
{
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'],
},
{
}, {
id: 'connection_string', label: gettext('Connection String'), type: 'multiline',
group: gettext('Connection'), mode: ['properties'], readonly: true,
}, {
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: obj.isShared,
depChange: (state)=>{
@@ -228,103 +215,13 @@ export default class ServerSchema extends BaseUISchema {
id: 'service', label: gettext('Service'), type: 'text',
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
group: gettext('Connection'),
},
{
id: 'sslmode', label: gettext('SSL mode'), type: 'select', group: gettext('SSL'),
controlProps: {
allowClear: false,
},
mode: ['properties', 'edit', 'create'], disabled: obj.isConnected,
options: [
{label: gettext('Allow'), value: 'allow'},
{label: gettext('Prefer'), value: 'prefer'},
{label: gettext('Require'), value: 'require'},
{label: gettext('Disable'), value: 'disable'},
{label: gettext('Verify-CA'), value: 'verify-ca'},
{label: gettext('Verify-Full'), value: 'verify-full'},
],
},
{
id: 'sslcert', label: gettext('Client certificate'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},
{
id: 'sslkey', label: gettext('Client certificate key'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},
{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['edit', 'create'], group: gettext('SSL'),
disabled: obj.isSSL, readonly: obj.isConnected,
deps: ['sslmode'],
},
{
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslcert = state.sslcert;
return !_.isUndefined(sslcert) && !_.isNull(sslcert);
},
},{
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslkey = state.sslkey;
return !_.isUndefined(sslkey) && !_.isNull(sslkey);
},
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslrootcert = state.sslrootcert;
return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
},
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslcrl = state.sslcrl;
return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
},
},{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['properties'], group: gettext('SSL'),
deps: ['sslmode'],
visible: function(state) {
return _.indexOf(obj.SSL_MODES, state.sslmode) != -1;
},
},
{
}, {
id: 'connection_params', label: gettext('Connection Parameters'),
type: 'collection', group: gettext('Parameters'),
schema: this.paramSchema, mode: ['edit', 'create'], uniqueCol: ['name'],
canAdd: (state)=> !obj.isConnected(state), canEdit: false,
canDelete: (state)=> !obj.isConnected(state),
}, {
id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
disabled: function() {
@@ -401,9 +298,6 @@ export default class ServerSchema extends BaseUISchema {
disabled: function(state) {
return (!current_user.allow_save_tunnel_password || !state.use_ssh_tunnel);
},
}, {
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
},
{
id: 'db_res', label: gettext('DB restriction'), type: 'select', group: gettext('Advanced'),
@@ -411,22 +305,6 @@ export default class ServerSchema extends BaseUISchema {
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected, controlProps: {
multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: 'Specify the databases to be restrict...'},
},
{
id: 'passfile', label: gettext('Password file'), type: 'file',
group: gettext('Advanced'), mode: ['edit', 'create'],
disabled: obj.isValidLib, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
},
{
id: 'passfile', label: gettext('Password file'), type: 'text',
group: gettext('Advanced'), mode: ['properties'],
visible: function(state) {
let passfile = state.passfile;
return !_.isUndefined(passfile) && !_.isNull(passfile);
},
},
{
id: 'passexec_cmd', label: gettext('Password exec command'), type: 'text',
group: gettext('Advanced'),
@@ -439,12 +317,6 @@ export default class ServerSchema extends BaseUISchema {
visible: function(state) {
return !_.isEmpty(state.passexec_cmd);
},
},
{
id: 'connect_timeout', label: gettext('Connection timeout (seconds)'),
type: 'int', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
min: 0,
}
];
}
@@ -453,30 +325,12 @@ export default class ServerSchema extends BaseUISchema {
let errmsg = null;
if (isEmptyString(state.service)) {
errmsg = gettext('Either Host name, Address or Service must be specified.');
if(isEmptyString(state.host) && isEmptyString(state.hostaddr)) {
errmsg = gettext('Either Host name or Service must be specified.');
if(isEmptyString(state.host)) {
setError('host', errmsg);
return true;
} else {
setError('host', null);
setError('hostaddr', null);
}
/* IP address validate */
if (state.hostaddr) {
try {
new Address4(state.hostaddr);
} catch(e) {
try {
new Address6(state.hostaddr);
} catch(ex) {
errmsg = gettext('Host address must be valid IPv4 or IPv6 address.');
setError('hostaddr', errmsg);
return true;
}
}
} else {
setError('hostaddr', null);
}
/* Hostname, IP address validate */
@@ -507,7 +361,7 @@ export default class ServerSchema extends BaseUISchema {
setError('port', null);
}
} else {
_.each(['host', 'hostaddr', 'db', 'username', 'port'], (item) => {
_.each(['host', 'db', 'username', 'port'], (item) => {
setError(item, null);
});
}
@@ -549,4 +403,92 @@ export default class ServerSchema extends BaseUISchema {
}
return false;
}
getConnectionParameters() {
return [{
'value': 'hostaddr', 'label': gettext('Host address'), 'vartype': 'string'
}, {
'value': 'passfile', 'label': gettext('Password file'), 'vartype': 'file'
}, {
'value': 'channel_binding', 'label': gettext('Channel binding'), 'vartype': 'enum',
'enumvals': [gettext('prefer'), gettext('require'), gettext('disable')],
'min_server_version': '13'
}, {
'value': 'connect_timeout', 'label': gettext('Connection timeout (seconds)'), 'vartype': 'integer'
}, {
'value': 'client_encoding', 'label': gettext('Client encoding'), 'vartype': 'string'
}, {
'value': 'options', 'label': gettext('Options'), 'vartype': 'string'
}, {
'value': 'application_name', 'label': gettext('Application name'), 'vartype': 'string'
}, {
'value': 'fallback_application_name', 'label': gettext('Fallback application name'), 'vartype': 'string'
}, {
'value': 'keepalives', 'label': gettext('Keepalives'), 'vartype': 'integer'
}, {
'value': 'keepalives_idle', 'label': gettext('Keepalives idle (seconds)'), 'vartype': 'integer'
}, {
'value': 'keepalives_interval', 'label': gettext('Keepalives interval (seconds)'), 'vartype': 'integer'
}, {
'value': 'keepalives_count', 'label': gettext('Keepalives count'), 'vartype': 'integer'
}, {
'value': 'tcp_user_timeout', 'label': gettext('TCP user timeout (milliseconds)'), 'vartype': 'integer',
'min_server_version': '12'
}, {
'value': 'tty', 'label': gettext('TTY'), 'vartype': 'string',
'max_server_version': '13'
}, {
'value': 'replication', 'label': gettext('Replication'), 'vartype': 'enum',
'enumvals': [gettext('on'), gettext('off'), gettext('database')],
'min_server_version': '11'
}, {
'value': 'gssencmode', 'label': gettext('GSS encmode'), 'vartype': 'enum',
'enumvals': [gettext('prefer'), gettext('require'), gettext('disable')],
'min_server_version': '12'
}, {
'value': 'sslmode', 'label': gettext('SSL mode'), 'vartype': 'enum',
'enumvals': [gettext('allow'), gettext('prefer'), gettext('require'),
gettext('disable'), gettext('verify-ca'), gettext('verify-full')]
}, {
'value': 'sslcompression', 'label': gettext('SSL compression?'), 'vartype': 'bool',
}, {
'value': 'sslcert', 'label': gettext('Client certificate'), 'vartype': 'file'
}, {
'value': 'sslkey', 'label': gettext('Client certificate key'), 'vartype': 'file'
}, {
'value': 'sslpassword', 'label': gettext('SSL password'), 'vartype': 'string',
'min_server_version': '13'
}, {
'value': 'sslrootcert', 'label': gettext('Root certificate'), 'vartype': 'file'
}, {
'value': 'sslcrl', 'label': gettext('Certificate revocation list'), 'vartype': 'file',
}, {
'value': 'sslcrldir', 'label': gettext('Certificate revocation list directory'), 'vartype': 'file',
'min_server_version': '14'
}, {
'value': 'sslsni', 'label': gettext('Server name indication'), 'vartype': 'bool',
'min_server_version': '14'
}, {
'value': 'requirepeer', 'label': gettext('Require peer'), 'vartype': 'string',
}, {
'value': 'ssl_min_protocol_version', 'label': gettext('SSL min protocol version'),
'vartype': 'enum', 'min_server_version': '13',
'enumvals': [gettext('TLSv1'), gettext('TLSv1.1'), gettext('TLSv1.2'),
gettext('TLSv1.3')]
}, {
'value': 'ssl_max_protocol_version', 'label': gettext('SSL max protocol version'),
'vartype': 'enum', 'min_server_version': '13',
'enumvals': [gettext('TLSv1'), gettext('TLSv1.1'), gettext('TLSv1.2'),
gettext('TLSv1.3')]
}, {
'value': 'krbsrvname', 'label': gettext('Kerberos service name'), 'vartype': 'string',
}, {
'value': 'gsslib', 'label': gettext('GSS library'), 'vartype': 'string',
}, {
'value': 'target_session_attrs', 'label': gettext('Target session attribute'),
'vartype': 'enum',
'enumvals': [gettext('any'), gettext('read-write'), gettext('read-only'),
gettext('primary'), gettext('standby'), gettext('prefer-standby')]
}];
}
}

View File

@@ -51,12 +51,18 @@ export default class VariableSchema extends BaseUISchema {
value: undefined,
role: null,
database: null,
keyword: null,
});
this.vnameOptions = vnameOptions;
this.databaseOptions = databaseOptions;
this.roleOptions = roleOptions;
this.varTypes = {};
this.keys = keys;
this.allReadOnly = false;
}
setAllReadOnly(isReadOnly) {
this.allReadOnly = isReadOnly;
}
setVarTypes(options) {
@@ -67,6 +73,19 @@ export default class VariableSchema extends BaseUISchema {
});
}
getPlaceHolderMsg(variable) {
let msg = '';
if (variable?.min_server_version && variable?.max_server_version) {
msg = gettext('%s <= Supported version >= %s', variable?.max_server_version, variable?.min_server_version);
} else if (variable?.min_server_version) {
msg = gettext('Supported version >= %s', variable?.min_server_version);
} else if (variable?.max_server_version) {
msg = gettext('Supported version <= %s', variable?.max_server_version);
}
return msg;
}
getValueFieldProps(variable) {
switch(variable?.vartype) {
case 'bool':
@@ -74,17 +93,44 @@ export default class VariableSchema extends BaseUISchema {
case 'enum':
return {
cell: 'select',
options: (variable.enumvals || []).map((val)=>({
options: (variable.enumvals || []).map((val)=>(typeof(val)=='string' ? {
label: val,
value: val
}))
}: val)),
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'integer':
return 'int';
return {
cell: 'int',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'real':
return 'numeric';
return {
cell: 'numeric',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'string':
return 'text';
return {
cell: 'text',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'file':
return {
cell: 'file',
controlProps: {
dialogType: 'select_file',
supportedTypes: ['*'],
placeholder: this.getPlaceHolderMsg(variable)
}
};
default:
return '';
}
@@ -99,8 +145,8 @@ export default class VariableSchema extends BaseUISchema {
},
{
id: 'name', label: gettext('Name'), type:'text',
readonly: function(state) {
return !obj.isNew(state);
editable: function(state) {
return obj.isNew(state) || !obj.allReadOnly;
},
cell: ()=>({
cell: 'select',
@@ -109,9 +155,16 @@ export default class VariableSchema extends BaseUISchema {
controlProps: { allowClear: false },
}),
},
{
id: 'keyword', label: gettext('Keyword'), type: '', cell: '',
deps: ['name'], minWidth: 25,
depChange: (state, source, topState, actionObj)=>{
return { keyword: actionObj.value };
}
},
{
id: 'value', label: gettext('Value'), type: 'text',
deps: ['name'],
deps: ['name'], editable: !obj.allReadOnly,
depChange: (state, source)=>{
if(source[source.length-1] == 'name') {
let variable = this.varTypes[state.name];