mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-14 01:13:49 -06:00
1) Replace the deprecated unit test method. 2) Wraps filter usage in a list call. 3) Converts the old metaclass syntax to new. 4) Use range instead of xrange method. 5) Change Unicode to str. 6) Several other transformations. 7) Fixed change password test cases. 8) Use simplejson instead of plain JSON.
1631 lines
57 KiB
Python
1631 lines
57 KiB
Python
##########################################################################
|
|
#
|
|
# pgAdmin 4 - PostgreSQL Tools
|
|
#
|
|
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
|
|
# This software is released under the PostgreSQL Licence
|
|
#
|
|
##########################################################################
|
|
|
|
import simplejson as json
|
|
import pgadmin.browser.server_groups as sg
|
|
from flask import render_template, request, make_response, jsonify, \
|
|
current_app, url_for
|
|
from flask_babelex import gettext
|
|
from flask_security import current_user, login_required
|
|
from pgadmin.browser.server_groups.servers.types import ServerType
|
|
from pgadmin.browser.utils import PGChildNodeView
|
|
from pgadmin.utils.ajax import make_json_response, bad_request, forbidden, \
|
|
make_response as ajax_response, internal_server_error, unauthorized, gone
|
|
from pgadmin.utils.crypto import encrypt, decrypt, pqencryptpassword
|
|
from pgadmin.utils.menu import MenuItem
|
|
from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
|
|
|
|
import config
|
|
from config import PG_DEFAULT_DRIVER
|
|
from pgadmin.model import db, Server, ServerGroup, User
|
|
from pgadmin.utils.driver import get_driver
|
|
from pgadmin.utils.master_password import get_crypt_key
|
|
from pgadmin.utils.exception import CryptKeyMissing
|
|
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
|
from psycopg2 import Error as psycopg2_Error, OperationalError
|
|
from pgadmin.browser.server_groups.servers.utils import is_valid_ipaddress
|
|
from pgadmin.utils.constants import UNAUTH_REQ, MIMETYPE_APP_JS, \
|
|
SERVER_CONNECTION_CLOSED
|
|
|
|
|
|
def has_any(data, keys):
|
|
"""
|
|
Checks any one of the keys present in the data given
|
|
"""
|
|
if data is None and not isinstance(data, dict):
|
|
return False
|
|
|
|
if keys is None and not isinstance(keys, list):
|
|
return False
|
|
|
|
for key in keys:
|
|
if key in data:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def recovery_state(connection, postgres_version):
|
|
recovery_check_sql = render_template(
|
|
"connect/sql/#{0}#/check_recovery.sql".format(postgres_version))
|
|
|
|
status, result = connection.execute_dict(recovery_check_sql)
|
|
if status and 'rows' in result and len(result['rows']) > 0:
|
|
in_recovery = result['rows'][0]['inrecovery']
|
|
wal_paused = result['rows'][0]['isreplaypaused']
|
|
else:
|
|
in_recovery = None
|
|
wal_paused = None
|
|
return status, result, in_recovery, wal_paused
|
|
|
|
|
|
def server_icon_and_background(is_connected, manager, server):
|
|
"""
|
|
|
|
Args:
|
|
is_connected: Flag to check if server is connected
|
|
manager: Connection manager
|
|
server: Sever object
|
|
|
|
Returns:
|
|
Server Icon CSS class
|
|
"""
|
|
server_background_color = ''
|
|
if server and server.bgcolor:
|
|
server_background_color = ' {0}'.format(
|
|
server.bgcolor
|
|
)
|
|
# If user has set font color also
|
|
if server.fgcolor:
|
|
server_background_color = '{0} {1}'.format(
|
|
server_background_color,
|
|
server.fgcolor
|
|
)
|
|
|
|
if is_connected:
|
|
return 'icon-{0}{1}'.format(
|
|
manager.server_type, server_background_color
|
|
)
|
|
else:
|
|
return 'icon-server-not-connected{0}'.format(
|
|
server_background_color
|
|
)
|
|
|
|
|
|
class ServerModule(sg.ServerGroupPluginModule):
|
|
_NODE_TYPE = "server"
|
|
LABEL = gettext("Servers")
|
|
|
|
@property
|
|
def node_type(self):
|
|
return self._NODE_TYPE
|
|
|
|
@property
|
|
def script_load(self):
|
|
"""
|
|
Load the module script for server, when any of the server-group node is
|
|
initialized.
|
|
"""
|
|
return sg.ServerGroupModule.node_type
|
|
|
|
@login_required
|
|
def get_nodes(self, gid):
|
|
"""Return a JSON document listing the server groups for the user"""
|
|
servers = Server.query.filter_by(user_id=current_user.id,
|
|
servergroup_id=gid)
|
|
|
|
driver = get_driver(PG_DEFAULT_DRIVER)
|
|
|
|
for server in servers:
|
|
connected = False
|
|
manager = None
|
|
errmsg = None
|
|
was_connected = False
|
|
in_recovery = None
|
|
wal_paused = None
|
|
try:
|
|
manager = driver.connection_manager(server.id)
|
|
conn = manager.connection()
|
|
was_connected = conn.wasConnected
|
|
except CryptKeyMissing:
|
|
# show the nodes at least even if not able to connect.
|
|
pass
|
|
except psycopg2_Error as e:
|
|
current_app.logger.exception(e)
|
|
errmsg = str(e)
|
|
|
|
yield self.generate_browser_node(
|
|
"%d" % (server.id),
|
|
gid,
|
|
server.name,
|
|
server_icon_and_background(connected, manager, server),
|
|
True,
|
|
self.node_type,
|
|
connected=connected,
|
|
server_type=manager.server_type if connected else "pg",
|
|
version=manager.version,
|
|
db=manager.db,
|
|
user=manager.user_info if connected else None,
|
|
in_recovery=in_recovery,
|
|
wal_pause=wal_paused,
|
|
is_password_saved=bool(server.save_password),
|
|
is_tunnel_password_saved=True
|
|
if server.tunnel_password is not None else False,
|
|
was_connected=was_connected,
|
|
errmsg=errmsg
|
|
)
|
|
|
|
@property
|
|
def jssnippets(self):
|
|
return []
|
|
|
|
@property
|
|
def csssnippets(self):
|
|
"""
|
|
Returns a snippet of css to include in the page
|
|
"""
|
|
snippets = [render_template("css/servers.css")]
|
|
|
|
for submodule in self.submodules:
|
|
snippets.extend(submodule.csssnippets)
|
|
|
|
for st in ServerType.types():
|
|
snippets.extend(st.csssnippets)
|
|
|
|
return snippets
|
|
|
|
def get_own_javascripts(self):
|
|
scripts = []
|
|
|
|
scripts.extend([{
|
|
'name': 'pgadmin.browser.server.privilege',
|
|
'path': url_for('%s.static' % self.name, filename='js/privilege'),
|
|
'when': self.node_type,
|
|
'is_template': False,
|
|
'deps': ['pgadmin.browser.node.ui']
|
|
}, {
|
|
'name': 'pgadmin.browser.server.variable',
|
|
'path': url_for('%s.static' % self.name, filename='js/variable'),
|
|
'when': self.node_type,
|
|
'is_template': False
|
|
}, {
|
|
'name': 'pgadmin.server.supported_servers',
|
|
'path': url_for('browser.index') + 'server/supported_servers',
|
|
'is_template': True,
|
|
'when': self.node_type
|
|
}])
|
|
scripts.extend(sg.ServerGroupPluginModule.get_own_javascripts(self))
|
|
|
|
return scripts
|
|
|
|
def register(self, app, options, first_registration=False):
|
|
"""
|
|
Override the default register function to automagically register
|
|
sub-modules at once.
|
|
"""
|
|
if first_registration:
|
|
driver = get_driver(PG_DEFAULT_DRIVER, app)
|
|
app.jinja_env.filters['qtLiteral'] = driver.qtLiteral
|
|
app.jinja_env.filters['qtIdent'] = driver.qtIdent
|
|
app.jinja_env.filters['qtTypeIdent'] = driver.qtTypeIdent
|
|
app.jinja_env.filters['hasAny'] = has_any
|
|
|
|
super(ServerModule, self).register(app, options, first_registration)
|
|
|
|
# We do not have any preferences for server node.
|
|
def register_preferences(self):
|
|
"""
|
|
register_preferences
|
|
Override it so that - it does not register the show_node preference for
|
|
server type.
|
|
"""
|
|
ServerType.register_preferences()
|
|
|
|
def get_exposed_url_endpoints(self):
|
|
return ['NODE-server.connect_id']
|
|
|
|
|
|
class ServerMenuItem(MenuItem):
|
|
def __init__(self, **kwargs):
|
|
kwargs.setdefault("type", ServerModule.node_type)
|
|
super(ServerMenuItem, self).__init__(**kwargs)
|
|
|
|
|
|
blueprint = ServerModule(__name__)
|
|
|
|
|
|
class ServerNode(PGChildNodeView):
|
|
node_type = ServerModule._NODE_TYPE
|
|
node_label = "Server"
|
|
|
|
parent_ids = [{'type': 'int', 'id': 'gid'}]
|
|
ids = [{'type': 'int', 'id': 'sid'}]
|
|
operations = dict({
|
|
'obj': [
|
|
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
|
|
{'get': 'list', 'post': 'create'}
|
|
],
|
|
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
|
'sql': [{'get': 'sql'}],
|
|
'msql': [{'get': 'modified_sql'}],
|
|
'stats': [{'get': 'statistics'}],
|
|
'dependency': [{'get': 'dependencies'}],
|
|
'dependent': [{'get': 'dependents'}],
|
|
'children': [{'get': 'children'}],
|
|
'supported_servers.js': [{}, {}, {'get': 'supported_servers'}],
|
|
'reload':
|
|
[{'get': 'reload_configuration'}],
|
|
'restore_point':
|
|
[{'post': 'create_restore_point'}],
|
|
'connect': [{
|
|
'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'
|
|
}],
|
|
'change_password': [{'post': 'change_password'}],
|
|
'wal_replay': [{
|
|
'delete': 'pause_wal_replay', 'put': 'resume_wal_replay'
|
|
}],
|
|
'check_pgpass': [{'get': 'check_pgpass'}],
|
|
'clear_saved_password': [{'put': 'clear_saved_password'}],
|
|
'clear_sshtunnel_password': [{'put': 'clear_sshtunnel_password'}]
|
|
})
|
|
SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full']
|
|
|
|
def check_ssl_fields(self, data):
|
|
"""
|
|
This function will allow us to check and set defaults for
|
|
SSL fields
|
|
|
|
Args:
|
|
data: Response data
|
|
|
|
Returns:
|
|
Flag and Data
|
|
"""
|
|
flag = False
|
|
|
|
if 'sslmode' in data and data['sslmode'] in self.SSL_MODES:
|
|
flag = True
|
|
ssl_fields = [
|
|
'sslcert', 'sslkey', 'sslrootcert', 'sslcrl', 'sslcompression'
|
|
]
|
|
# Required SSL fields for SERVER mode from user
|
|
required_ssl_fields_server_mode = ['sslcert', 'sslkey']
|
|
|
|
for field in ssl_fields:
|
|
if field in data:
|
|
continue
|
|
elif config.SERVER_MODE and \
|
|
field in required_ssl_fields_server_mode:
|
|
# In Server mode,
|
|
# we will set dummy SSL certificate file path which will
|
|
# prevent using default SSL certificates from web servers
|
|
|
|
# Set file manager directory from preference
|
|
import os
|
|
file_extn = '.key' if field.endswith('key') else '.crt'
|
|
dummy_ssl_file = os.path.join(
|
|
'<STORAGE_DIR>', '.postgresql',
|
|
'postgresql' + file_extn
|
|
)
|
|
data[field] = dummy_ssl_file
|
|
# For Desktop mode, we will allow to default
|
|
else:
|
|
data[field] = None
|
|
|
|
return flag, data
|
|
|
|
@login_required
|
|
def nodes(self, gid):
|
|
res = []
|
|
"""
|
|
Return a JSON document listing the servers under this server group
|
|
for the user.
|
|
"""
|
|
servers = Server.query.filter_by(user_id=current_user.id,
|
|
servergroup_id=gid)
|
|
|
|
driver = get_driver(PG_DEFAULT_DRIVER)
|
|
|
|
for server in servers:
|
|
manager = driver.connection_manager(server.id)
|
|
conn = manager.connection()
|
|
connected = conn.connected()
|
|
errmsg = None
|
|
in_recovery = None
|
|
wal_paused = None
|
|
if connected:
|
|
status, result, in_recovery, wal_paused =\
|
|
recovery_state(conn, manager.version)
|
|
if not status:
|
|
connected = False
|
|
manager.release()
|
|
errmsg = "{0} : {1}".format(server.name, result)
|
|
|
|
res.append(
|
|
self.blueprint.generate_browser_node(
|
|
"%d" % (server.id),
|
|
gid,
|
|
server.name,
|
|
server_icon_and_background(connected, manager, server),
|
|
True,
|
|
self.node_type,
|
|
connected=connected,
|
|
server_type=manager.server_type if connected else 'pg',
|
|
version=manager.version,
|
|
db=manager.db,
|
|
user=manager.user_info if connected else None,
|
|
in_recovery=in_recovery,
|
|
wal_pause=wal_paused,
|
|
is_password_saved=bool(server.save_password),
|
|
is_tunnel_password_saved=True
|
|
if server.tunnel_password is not None else False,
|
|
errmsg=errmsg
|
|
)
|
|
)
|
|
|
|
if not len(res):
|
|
return gone(errormsg=gettext(
|
|
'The specified server group with id# {0} could not be found.'
|
|
))
|
|
|
|
return make_json_response(result=res)
|
|
|
|
@login_required
|
|
def node(self, gid, sid):
|
|
"""Return a JSON document listing the server groups for the user"""
|
|
server = Server.query.filter_by(user_id=current_user.id,
|
|
servergroup_id=gid,
|
|
id=sid).first()
|
|
|
|
if server is None:
|
|
return make_json_response(
|
|
status=410,
|
|
success=0,
|
|
errormsg=gettext(
|
|
gettext(
|
|
"Could not find the server with id# {0}."
|
|
).format(sid)
|
|
)
|
|
)
|
|
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(server.id)
|
|
conn = manager.connection()
|
|
connected = conn.connected()
|
|
errmsg = None
|
|
in_recovery = None
|
|
wal_paused = None
|
|
if connected:
|
|
status, result, in_recovery, wal_paused =\
|
|
recovery_state(conn, manager.version)
|
|
if not status:
|
|
connected = False
|
|
manager.release()
|
|
errmsg = "{0} : {1}".format(server.name, result)
|
|
|
|
return make_json_response(
|
|
result=self.blueprint.generate_browser_node(
|
|
"%d" % (server.id),
|
|
gid,
|
|
server.name,
|
|
server_icon_and_background(connected, manager, server),
|
|
True,
|
|
self.node_type,
|
|
connected=connected,
|
|
server_type=manager.server_type if connected else 'pg',
|
|
version=manager.version,
|
|
db=manager.db,
|
|
user=manager.user_info if connected else None,
|
|
in_recovery=in_recovery,
|
|
wal_pause=wal_paused,
|
|
is_password_saved=bool(server.save_password),
|
|
is_tunnel_password_saved=True
|
|
if server.tunnel_password is not None else False,
|
|
errmsg=errmsg
|
|
),
|
|
)
|
|
|
|
@login_required
|
|
def delete(self, gid, sid):
|
|
"""Delete a server node in the settings database."""
|
|
servers = Server.query.filter_by(user_id=current_user.id, id=sid)
|
|
|
|
# TODO:: A server, which is connected, cannot be deleted
|
|
if servers is None:
|
|
return make_json_response(
|
|
status=410,
|
|
success=0,
|
|
errormsg=gettext(
|
|
'The specified server could not be found.\n'
|
|
'Does the user have permission to access the '
|
|
'server?'
|
|
)
|
|
)
|
|
else:
|
|
try:
|
|
for s in servers:
|
|
get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id)
|
|
db.session.delete(s)
|
|
db.session.commit()
|
|
|
|
QueryHistory.clear_history(current_user.id, sid)
|
|
|
|
except Exception as e:
|
|
current_app.logger.exception(e)
|
|
return make_json_response(
|
|
success=0,
|
|
errormsg=e.message)
|
|
|
|
return make_json_response(success=1,
|
|
info=gettext("Server deleted"))
|
|
|
|
@login_required
|
|
def update(self, gid, sid):
|
|
"""Update the server settings"""
|
|
server = Server.query.filter_by(
|
|
user_id=current_user.id, id=sid).first()
|
|
|
|
if server is None:
|
|
return make_json_response(
|
|
status=410,
|
|
success=0,
|
|
errormsg=gettext("Could not find the required server.")
|
|
)
|
|
|
|
# Not all parameters can be modified, while the server is connected
|
|
config_param_map = {
|
|
'name': 'name',
|
|
'host': 'host',
|
|
'hostaddr': 'hostaddr',
|
|
'port': 'port',
|
|
'db': 'maintenance_db',
|
|
'username': 'username',
|
|
'sslmode': 'ssl_mode',
|
|
'gid': 'servergroup_id',
|
|
'comment': 'comment',
|
|
'role': 'role',
|
|
'db_res': 'db_res',
|
|
'passfile': 'passfile',
|
|
'sslcert': 'sslcert',
|
|
'sslkey': 'sslkey',
|
|
'sslrootcert': 'sslrootcert',
|
|
'sslcrl': 'sslcrl',
|
|
'sslcompression': 'sslcompression',
|
|
'bgcolor': 'bgcolor',
|
|
'fgcolor': 'fgcolor',
|
|
'service': 'service',
|
|
'connect_timeout': 'connect_timeout',
|
|
'use_ssh_tunnel': 'use_ssh_tunnel',
|
|
'tunnel_host': 'tunnel_host',
|
|
'tunnel_port': 'tunnel_port',
|
|
'tunnel_username': 'tunnel_username',
|
|
'tunnel_authentication': 'tunnel_authentication',
|
|
'tunnel_identity_file': 'tunnel_identity_file',
|
|
}
|
|
|
|
disp_lbl = {
|
|
'name': gettext('name'),
|
|
'host': gettext('Host name/address'),
|
|
'port': gettext('Port'),
|
|
'db': gettext('Maintenance database'),
|
|
'username': gettext('Username'),
|
|
'sslmode': gettext('SSL Mode'),
|
|
'comment': gettext('Comments'),
|
|
'role': gettext('Role')
|
|
}
|
|
|
|
idx = 0
|
|
data = request.form if request.form else json.loads(
|
|
request.data, encoding='utf-8'
|
|
)
|
|
if 'db_res' in data:
|
|
data['db_res'] = ','.join(data['db_res'])
|
|
|
|
hostaddr = data.get('hostaddr')
|
|
if hostaddr and not is_valid_ipaddress(hostaddr):
|
|
return make_json_response(
|
|
success=0,
|
|
status=400,
|
|
errormsg=gettext('Host address not valid')
|
|
)
|
|
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
connected = conn.connected()
|
|
|
|
self._server_modify_disallowed_when_connected(
|
|
connected, data, disp_lbl)
|
|
|
|
idx = self._set_valid_attr_value(data, config_param_map, server)
|
|
|
|
if idx == 0:
|
|
return make_json_response(
|
|
success=0,
|
|
errormsg=gettext('No parameters were changed.')
|
|
)
|
|
|
|
try:
|
|
db.session.commit()
|
|
except Exception as e:
|
|
current_app.logger.exception(e)
|
|
return make_json_response(
|
|
success=0,
|
|
errormsg=e.message
|
|
)
|
|
|
|
# When server is connected, we don't require to update the connection
|
|
# manager. Because - we don't allow to change any of the parameters,
|
|
# which will affect the connections.
|
|
if not conn.connected():
|
|
manager.update(server)
|
|
|
|
return jsonify(
|
|
node=self.blueprint.generate_browser_node(
|
|
"%d" % (server.id), server.servergroup_id,
|
|
server.name,
|
|
server_icon_and_background(connected, manager, server),
|
|
True,
|
|
self.node_type,
|
|
connected=connected,
|
|
user=manager.user_info if connected else None,
|
|
server_type='pg' # default server type
|
|
)
|
|
)
|
|
|
|
def _set_valid_attr_value(self, data, config_param_map, server):
|
|
|
|
idx = 0
|
|
for arg in config_param_map:
|
|
if arg in data:
|
|
value = data[arg]
|
|
# sqlite3 do not have boolean type so we need to convert
|
|
# it manually to integer
|
|
if arg == 'sslcompression':
|
|
value = 1 if value else 0
|
|
setattr(server, config_param_map[arg], value)
|
|
idx += 1
|
|
|
|
return idx
|
|
|
|
def _server_modify_disallowed_when_connected(
|
|
self, connected, data, disp_lbl):
|
|
|
|
if connected:
|
|
for arg in (
|
|
'hostaddr', 'db', 'sslmode',
|
|
'role', 'service'
|
|
):
|
|
if arg in data:
|
|
return forbidden(
|
|
errmsg=gettext(
|
|
"'{0}' is not allowed to modify, "
|
|
"when server is connected."
|
|
).format(disp_lbl[arg])
|
|
)
|
|
|
|
@login_required
|
|
def list(self, gid):
|
|
"""
|
|
Return list of attributes of all servers.
|
|
"""
|
|
servers = Server.query.filter_by(
|
|
user_id=current_user.id,
|
|
servergroup_id=gid).order_by(Server.name)
|
|
sg = ServerGroup.query.filter_by(
|
|
user_id=current_user.id,
|
|
id=gid
|
|
).first()
|
|
res = []
|
|
|
|
driver = get_driver(PG_DEFAULT_DRIVER)
|
|
|
|
for server in servers:
|
|
manager = driver.connection_manager(server.id)
|
|
conn = manager.connection()
|
|
connected = conn.connected()
|
|
|
|
res.append({
|
|
'id': server.id,
|
|
'name': server.name,
|
|
'host': server.host,
|
|
'port': server.port,
|
|
'db': server.maintenance_db,
|
|
'username': server.username,
|
|
'gid': server.servergroup_id,
|
|
'group-name': sg.name,
|
|
'comment': server.comment,
|
|
'role': server.role,
|
|
'connected': connected,
|
|
'version': manager.ver,
|
|
'server_type': manager.server_type if connected else 'pg',
|
|
'db_res': server.db_res.split(',') if server.db_res else None
|
|
})
|
|
|
|
return ajax_response(
|
|
response=res
|
|
)
|
|
|
|
@login_required
|
|
def properties(self, gid, sid):
|
|
"""Return list of attributes of a server"""
|
|
server = Server.query.filter_by(
|
|
user_id=current_user.id,
|
|
id=sid).first()
|
|
|
|
if server is None:
|
|
return make_json_response(
|
|
status=410,
|
|
success=0,
|
|
errormsg=self.not_found_error_msg()
|
|
)
|
|
|
|
sg = ServerGroup.query.filter_by(
|
|
user_id=current_user.id,
|
|
id=server.servergroup_id
|
|
).first()
|
|
|
|
driver = get_driver(PG_DEFAULT_DRIVER)
|
|
|
|
manager = driver.connection_manager(sid)
|
|
conn = manager.connection()
|
|
connected = conn.connected()
|
|
|
|
is_ssl = True if server.ssl_mode in self.SSL_MODES else False
|
|
|
|
return ajax_response(
|
|
response={
|
|
'id': server.id,
|
|
'name': server.name,
|
|
'host': server.host,
|
|
'hostaddr': server.hostaddr,
|
|
'port': server.port,
|
|
'db': server.maintenance_db,
|
|
'username': server.username,
|
|
'gid': str(server.servergroup_id),
|
|
'group-name': sg.name,
|
|
'comment': server.comment,
|
|
'role': server.role,
|
|
'connected': connected,
|
|
'version': manager.ver,
|
|
'sslmode': server.ssl_mode,
|
|
'server_type': manager.server_type if connected else 'pg',
|
|
'bgcolor': server.bgcolor,
|
|
'fgcolor': server.fgcolor,
|
|
'db_res': server.db_res.split(',') if server.db_res else None,
|
|
'passfile': server.passfile if server.passfile else None,
|
|
'sslcert': server.sslcert if is_ssl else None,
|
|
'sslkey': server.sslkey if is_ssl else None,
|
|
'sslrootcert': server.sslrootcert if is_ssl else None,
|
|
'sslcrl': server.sslcrl if is_ssl else None,
|
|
'sslcompression': True if is_ssl and server.sslcompression
|
|
else False,
|
|
'service': server.service if server.service else None,
|
|
'connect_timeout':
|
|
server.connect_timeout if server.connect_timeout else 0,
|
|
'use_ssh_tunnel': server.use_ssh_tunnel
|
|
if server.use_ssh_tunnel else 0,
|
|
'tunnel_host': server.tunnel_host if server.tunnel_host
|
|
else None,
|
|
'tunnel_port': server.tunnel_port if server.tunnel_port
|
|
else 22,
|
|
'tunnel_username': server.tunnel_username
|
|
if server.tunnel_username else None,
|
|
'tunnel_identity_file': server.tunnel_identity_file
|
|
if server.tunnel_identity_file else None,
|
|
'tunnel_authentication': server.tunnel_authentication
|
|
if server.tunnel_authentication else 0
|
|
}
|
|
)
|
|
|
|
@login_required
|
|
def create(self, gid):
|
|
"""Add a server node to the settings database"""
|
|
required_args = [
|
|
'name',
|
|
'db',
|
|
'sslmode',
|
|
]
|
|
|
|
data = request.form if request.form else json.loads(
|
|
request.data, encoding='utf-8'
|
|
)
|
|
|
|
# Get enc key
|
|
crypt_key_present, crypt_key = get_crypt_key()
|
|
if not crypt_key_present:
|
|
raise CryptKeyMissing
|
|
|
|
# Some fields can be provided with service file so they are optional
|
|
if 'service' in data and not data['service']:
|
|
required_args.extend([
|
|
'host',
|
|
'port',
|
|
'username',
|
|
'role'
|
|
])
|
|
for arg in required_args:
|
|
if arg not in data:
|
|
return make_json_response(
|
|
status=410,
|
|
success=0,
|
|
errormsg=gettext(
|
|
"Could not find the required parameter ({})."
|
|
).format(arg)
|
|
)
|
|
|
|
hostaddr = data.get('hostaddr')
|
|
if hostaddr and not is_valid_ipaddress(data['hostaddr']):
|
|
return make_json_response(
|
|
success=0,
|
|
status=400,
|
|
errormsg=gettext('Not a valid Host address')
|
|
)
|
|
|
|
# To check ssl configuration
|
|
is_ssl, data = self.check_ssl_fields(data)
|
|
|
|
server = None
|
|
|
|
try:
|
|
server = Server(
|
|
user_id=current_user.id,
|
|
servergroup_id=data.get('gid', gid),
|
|
name=data.get('name'),
|
|
host=data.get('host', None),
|
|
hostaddr=hostaddr,
|
|
port=data.get('port'),
|
|
maintenance_db=data.get('db', None),
|
|
username=data.get('username'),
|
|
save_password=1 if data.get('save_password', False) and
|
|
config.ALLOW_SAVE_PASSWORD else 0,
|
|
ssl_mode=data.get('sslmode'),
|
|
comment=data.get('comment', None),
|
|
role=data.get('role', None),
|
|
db_res=','.join(data['db_res'])
|
|
if 'db_res' in data else None,
|
|
sslcert=data.get('sslcert', None),
|
|
sslkey=data.get('sslkey', None),
|
|
sslrootcert=data.get('sslrootcert', None),
|
|
sslcrl=data.get('sslcrl', None),
|
|
sslcompression=1 if is_ssl and data['sslcompression'] else 0,
|
|
bgcolor=data.get('bgcolor', None),
|
|
fgcolor=data.get('fgcolor', None),
|
|
service=data.get('service', None),
|
|
connect_timeout=data.get('connect_timeout', 0),
|
|
use_ssh_tunnel=data.get('use_ssh_tunnel', 0),
|
|
tunnel_host=data.get('tunnel_host', None),
|
|
tunnel_port=data.get('tunnel_port', 22),
|
|
tunnel_username=data.get('tunnel_username', None),
|
|
tunnel_authentication=data.get('tunnel_authentication', 0),
|
|
tunnel_identity_file=data.get('tunnel_identity_file', None)
|
|
)
|
|
db.session.add(server)
|
|
db.session.commit()
|
|
|
|
connected = False
|
|
user = None
|
|
manager = None
|
|
|
|
if 'connect_now' in data and data['connect_now']:
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
|
|
server.id)
|
|
manager.update(server)
|
|
conn = manager.connection()
|
|
|
|
have_password = False
|
|
have_tunnel_password = False
|
|
password = None
|
|
passfile = None
|
|
tunnel_password = ''
|
|
if 'password' in data and data["password"] != '':
|
|
# login with password
|
|
have_password = True
|
|
password = data['password']
|
|
password = encrypt(password, crypt_key)
|
|
elif 'passfile' in data and data["passfile"] != '':
|
|
passfile = data['passfile']
|
|
setattr(server, 'passfile', passfile)
|
|
db.session.commit()
|
|
|
|
if 'tunnel_password' in data and data["tunnel_password"] != '':
|
|
have_tunnel_password = True
|
|
tunnel_password = data['tunnel_password']
|
|
tunnel_password = \
|
|
encrypt(tunnel_password, crypt_key)
|
|
|
|
status, errmsg = conn.connect(
|
|
password=password,
|
|
passfile=passfile,
|
|
tunnel_password=tunnel_password,
|
|
server_types=ServerType.types()
|
|
)
|
|
if not status:
|
|
db.session.delete(server)
|
|
db.session.commit()
|
|
return make_json_response(
|
|
status=401,
|
|
success=0,
|
|
errormsg=gettext(
|
|
"Unable to connect to server:\n\n{}"
|
|
).format(errmsg)
|
|
)
|
|
else:
|
|
if 'save_password' in data and data['save_password'] and \
|
|
have_password and config.ALLOW_SAVE_PASSWORD:
|
|
setattr(server, 'password', password)
|
|
db.session.commit()
|
|
|
|
if 'save_tunnel_password' in data and \
|
|
data['save_tunnel_password'] and \
|
|
have_tunnel_password and \
|
|
config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
|
setattr(server, 'tunnel_password', tunnel_password)
|
|
db.session.commit()
|
|
|
|
user = manager.user_info
|
|
connected = True
|
|
|
|
return jsonify(
|
|
node=self.blueprint.generate_browser_node(
|
|
"%d" % server.id, server.servergroup_id,
|
|
server.name,
|
|
server_icon_and_background(connected, manager, server),
|
|
True,
|
|
self.node_type,
|
|
user=user,
|
|
connected=connected,
|
|
server_type=manager.server_type
|
|
if manager and manager.server_type
|
|
else 'pg',
|
|
version=manager.version
|
|
if manager and manager.version
|
|
else None
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
if server:
|
|
db.session.delete(server)
|
|
db.session.commit()
|
|
|
|
current_app.logger.exception(e)
|
|
return make_json_response(
|
|
status=410,
|
|
success=0,
|
|
errormsg=str(e)
|
|
)
|
|
|
|
@login_required
|
|
def sql(self, gid, sid):
|
|
return make_json_response(data='')
|
|
|
|
@login_required
|
|
def modified_sql(self, gid, sid):
|
|
return make_json_response(data='')
|
|
|
|
@login_required
|
|
def statistics(self, gid, sid):
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
|
|
if conn.connected():
|
|
status, res = conn.execute_dict(
|
|
render_template(
|
|
"/servers/sql/#{0}#/stats.sql".format(manager.version),
|
|
conn=conn, _=gettext
|
|
)
|
|
)
|
|
|
|
if not status:
|
|
return internal_server_error(errormsg=res)
|
|
|
|
return make_json_response(data=res)
|
|
|
|
return make_json_response(
|
|
info=gettext(
|
|
"Server has no active connection for generating statistics."
|
|
)
|
|
)
|
|
|
|
@login_required
|
|
def dependencies(self, gid, sid):
|
|
return make_json_response(data='')
|
|
|
|
@login_required
|
|
def dependents(self, gid, sid):
|
|
return make_json_response(data='')
|
|
|
|
def supported_servers(self, **kwargs):
|
|
"""
|
|
This property defines (if javascript) exists for this node.
|
|
Override this property for your own logic.
|
|
"""
|
|
|
|
return make_response(
|
|
render_template(
|
|
"servers/supported_servers.js",
|
|
server_types=ServerType.types()
|
|
),
|
|
200, {'Content-Type': MIMETYPE_APP_JS}
|
|
)
|
|
|
|
def connect_status(self, gid, sid):
|
|
"""Check and return the connection status."""
|
|
server = Server.query.filter_by(id=sid).first()
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
connected = conn.connected()
|
|
in_recovery = None
|
|
wal_paused = None
|
|
errmsg = None
|
|
if connected:
|
|
status, result, in_recovery, wal_paused =\
|
|
recovery_state(conn, manager.version)
|
|
|
|
if not status:
|
|
connected = False
|
|
manager.release()
|
|
errmsg = "{0} : {1}".format(server.name, result)
|
|
|
|
return make_json_response(
|
|
data={
|
|
'icon': server_icon_and_background(connected, manager, server),
|
|
'connected': connected,
|
|
'in_recovery': in_recovery,
|
|
'wal_pause': wal_paused,
|
|
'server_type': manager.server_type if connected else "pg",
|
|
'user': manager.user_info if connected else None,
|
|
'errmsg': errmsg
|
|
}
|
|
)
|
|
|
|
def connect(self, gid, sid):
|
|
"""
|
|
Connect the Server and return the connection object.
|
|
Verification Process before Connection:
|
|
Verify requested server.
|
|
|
|
Check the server password is already been stored in the
|
|
database or not.
|
|
If Yes, connect the server and return connection.
|
|
If No, Raise HTTP error and ask for the password.
|
|
|
|
In case of 'Save Password' request from user, excrypted Pasword
|
|
will be stored in the respected server database and
|
|
establish the connection OR just connect the server and do not
|
|
store the password.
|
|
"""
|
|
current_app.logger.info(
|
|
'Connection Request for server#{0}'.format(sid)
|
|
)
|
|
|
|
# Fetch Server Details
|
|
server = Server.query.filter_by(id=sid).first()
|
|
if server is None:
|
|
return bad_request(self.not_found_error_msg())
|
|
|
|
if current_user and hasattr(current_user, 'id'):
|
|
# Fetch User Details.
|
|
user = User.query.filter_by(id=current_user.id).first()
|
|
if user is None:
|
|
return unauthorized(gettext(UNAUTH_REQ))
|
|
else:
|
|
return unauthorized(gettext(UNAUTH_REQ))
|
|
|
|
data = {}
|
|
if request.form:
|
|
data = request.form
|
|
elif request.data:
|
|
data = json.loads(request.data, encoding='utf-8')
|
|
|
|
password = None
|
|
passfile = None
|
|
tunnel_password = None
|
|
save_password = False
|
|
save_tunnel_password = False
|
|
prompt_password = False
|
|
prompt_tunnel_password = False
|
|
|
|
# Connect the Server
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
manager.update(server)
|
|
conn = manager.connection()
|
|
|
|
# Get enc key
|
|
crypt_key_present, crypt_key = get_crypt_key()
|
|
if not crypt_key_present:
|
|
raise CryptKeyMissing
|
|
|
|
# If server using SSH Tunnel
|
|
if server.use_ssh_tunnel:
|
|
if 'tunnel_password' not in data:
|
|
if server.tunnel_password is None:
|
|
prompt_tunnel_password = True
|
|
else:
|
|
tunnel_password = server.tunnel_password
|
|
else:
|
|
tunnel_password = data['tunnel_password'] \
|
|
if 'tunnel_password' in data else ''
|
|
save_tunnel_password = data['save_tunnel_password'] \
|
|
if tunnel_password and 'save_tunnel_password' in data \
|
|
else False
|
|
# Encrypt the password before saving with user's login
|
|
# password key.
|
|
try:
|
|
tunnel_password = encrypt(tunnel_password, crypt_key) \
|
|
if tunnel_password is not None else \
|
|
server.tunnel_password
|
|
except Exception as e:
|
|
current_app.logger.exception(e)
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
if 'password' not in data:
|
|
conn_passwd = getattr(conn, 'password', None)
|
|
if conn_passwd is None and not server.save_password and \
|
|
server.passfile is None and server.service is None:
|
|
prompt_password = True
|
|
elif server.passfile and server.passfile != '':
|
|
passfile = server.passfile
|
|
else:
|
|
password = conn_passwd or server.password
|
|
else:
|
|
password = data['password'] if 'password' in data else None
|
|
save_password = data['save_password']\
|
|
if 'save_password' in data else False
|
|
|
|
# Encrypt the password before saving with user's login
|
|
# password key.
|
|
try:
|
|
password = encrypt(password, crypt_key) \
|
|
if password is not None else server.password
|
|
except Exception as e:
|
|
current_app.logger.exception(e)
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
# Check do we need to prompt for the database server or ssh tunnel
|
|
# password or both. Return the password template in case password is
|
|
# not provided, or password has not been saved earlier.
|
|
if prompt_password or prompt_tunnel_password:
|
|
return self.get_response_for_password(server, 428, prompt_password,
|
|
prompt_tunnel_password)
|
|
|
|
status = True
|
|
try:
|
|
status, errmsg = conn.connect(
|
|
password=password,
|
|
passfile=passfile,
|
|
tunnel_password=tunnel_password,
|
|
server_types=ServerType.types()
|
|
)
|
|
except OperationalError as e:
|
|
return internal_server_error(errormsg=str(e))
|
|
except Exception as e:
|
|
current_app.logger.exception(e)
|
|
return self.get_response_for_password(
|
|
server, 401, True, True, getattr(e, 'message', str(e)))
|
|
|
|
if not status:
|
|
|
|
current_app.logger.error(
|
|
"Could not connect to server(#{0}) - '{1}'.\nError: {2}"
|
|
.format(server.id, server.name, errmsg)
|
|
)
|
|
return self.get_response_for_password(server, 401, True,
|
|
True, errmsg)
|
|
else:
|
|
if save_password and config.ALLOW_SAVE_PASSWORD:
|
|
try:
|
|
# If DB server is running in trust mode then password may
|
|
# not be available but we don't need to ask password
|
|
# every time user try to connect
|
|
# 1 is True in SQLite as no boolean type
|
|
setattr(server, 'save_password', 1)
|
|
# Save the encrypted password using the user's login
|
|
# password key, if there is any password to save
|
|
if password:
|
|
setattr(server, 'password', password)
|
|
db.session.commit()
|
|
except Exception as e:
|
|
# Release Connection
|
|
current_app.logger.exception(e)
|
|
manager.release(database=server.maintenance_db)
|
|
conn = None
|
|
|
|
return internal_server_error(errormsg=e.message)
|
|
|
|
if save_tunnel_password and config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
|
try:
|
|
# Save the encrypted tunnel password.
|
|
setattr(server, 'tunnel_password', tunnel_password)
|
|
db.session.commit()
|
|
except Exception as e:
|
|
# Release Connection
|
|
current_app.logger.exception(e)
|
|
manager.release(database=server.maintenance_db)
|
|
conn = None
|
|
|
|
return internal_server_error(errormsg=e.message)
|
|
|
|
current_app.logger.info('Connection Established for server: \
|
|
%s - %s' % (server.id, server.name))
|
|
# Update the recovery and wal pause option for the server
|
|
# if connected successfully
|
|
_, _, in_recovery, wal_paused =\
|
|
recovery_state(conn, manager.version)
|
|
|
|
return make_json_response(
|
|
success=1,
|
|
info=gettext("Server connected."),
|
|
data={
|
|
'icon': server_icon_and_background(True, manager, server),
|
|
'connected': True,
|
|
'server_type': manager.server_type,
|
|
'type': manager.server_type,
|
|
'version': manager.version,
|
|
'db': manager.db,
|
|
'user': manager.user_info,
|
|
'in_recovery': in_recovery,
|
|
'wal_pause': wal_paused,
|
|
'is_password_saved': bool(server.save_password),
|
|
'is_tunnel_password_saved': True
|
|
if server.tunnel_password is not None else False,
|
|
}
|
|
)
|
|
|
|
def disconnect(self, gid, sid):
|
|
"""Disconnect the Server."""
|
|
|
|
server = Server.query.filter_by(id=sid).first()
|
|
if server is None:
|
|
return bad_request(self.not_found_error_msg())
|
|
|
|
# Release Connection
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
|
|
status = manager.release()
|
|
|
|
if not status:
|
|
return unauthorized(gettext("Server could not be disconnected."))
|
|
else:
|
|
return make_json_response(
|
|
success=1,
|
|
info=gettext("Server disconnected."),
|
|
data={
|
|
'icon': server_icon_and_background(False, manager, server),
|
|
'connected': False
|
|
}
|
|
)
|
|
|
|
def reload_configuration(self, gid, sid):
|
|
"""Reload the server configuration"""
|
|
|
|
# Reload the server configurations
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
|
|
if conn.connected():
|
|
# Execute the command for reload configuration for the server
|
|
status, rid = conn.execute_scalar("SELECT pg_reload_conf();")
|
|
|
|
if not status:
|
|
return internal_server_error(
|
|
gettext("Could not reload the server configuration.")
|
|
)
|
|
else:
|
|
return make_json_response(data={
|
|
'status': True,
|
|
'result': gettext('Server configuration reloaded.')
|
|
})
|
|
|
|
else:
|
|
return make_json_response(data={
|
|
'status': False,
|
|
'result': SERVER_CONNECTION_CLOSED})
|
|
|
|
def create_restore_point(self, gid, sid):
|
|
"""
|
|
This method will creates named restore point
|
|
|
|
Args:
|
|
gid: Server group ID
|
|
sid: Server ID
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
try:
|
|
data = request.form
|
|
restore_point_name = data['value'] if data else None
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
|
|
# Execute SQL to create named restore point
|
|
if conn.connected():
|
|
if restore_point_name:
|
|
status, res = conn.execute_scalar(
|
|
"SELECT pg_create_restore_point('{0}');".format(
|
|
restore_point_name
|
|
)
|
|
)
|
|
if not status:
|
|
return internal_server_error(
|
|
errormsg=str(res)
|
|
)
|
|
|
|
return make_json_response(
|
|
data={
|
|
'status': 1,
|
|
'result': gettext(
|
|
'Named restore point created: {0}').format(
|
|
restore_point_name)
|
|
})
|
|
|
|
except Exception as e:
|
|
current_app.logger.error(gettext(
|
|
'Named restore point creation failed ({0})').format(
|
|
str(e))
|
|
)
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
def change_password(self, gid, sid):
|
|
"""
|
|
This function is used to change the password of the
|
|
Database Server.
|
|
|
|
Args:
|
|
gid: Group id
|
|
sid: Server id
|
|
"""
|
|
try:
|
|
data = json.loads(request.form['data'], encoding='utf-8')
|
|
crypt_key = get_crypt_key()[1]
|
|
|
|
# Fetch Server Details
|
|
server = Server.query.filter_by(id=sid).first()
|
|
if server is None:
|
|
return bad_request(self.not_found_error_msg())
|
|
|
|
# Fetch User Details.
|
|
user = User.query.filter_by(id=current_user.id).first()
|
|
if user is None:
|
|
return unauthorized(gettext(UNAUTH_REQ))
|
|
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
is_passfile = False
|
|
|
|
# If there is no password found for the server
|
|
# then check for pgpass file
|
|
if not server.password and not manager.password and \
|
|
server.passfile and manager.passfile and \
|
|
server.passfile == manager.passfile:
|
|
is_passfile = True
|
|
|
|
# Check for password only if there is no pgpass file used
|
|
if not is_passfile and data and \
|
|
('password' not in data or data['password'] == ''):
|
|
return make_json_response(
|
|
status=400,
|
|
success=0,
|
|
errormsg=gettext(
|
|
"Could not find the required parameter(s)."
|
|
)
|
|
)
|
|
|
|
if data and ('newPassword' not in data or
|
|
data['newPassword'] == '' or
|
|
'confirmPassword' not in data or
|
|
data['confirmPassword'] == ''):
|
|
return make_json_response(
|
|
status=400,
|
|
success=0,
|
|
errormsg=gettext(
|
|
"Could not find the required parameter(s)."
|
|
)
|
|
)
|
|
|
|
if data['newPassword'] != data['confirmPassword']:
|
|
return make_json_response(
|
|
status=200,
|
|
success=0,
|
|
errormsg=gettext(
|
|
"Passwords do not match."
|
|
)
|
|
)
|
|
|
|
# Check against old password only if no pgpass file
|
|
if not is_passfile:
|
|
decrypted_password = decrypt(manager.password, crypt_key)
|
|
|
|
if isinstance(decrypted_password, bytes):
|
|
decrypted_password = decrypted_password.decode()
|
|
|
|
password = data['password']
|
|
|
|
# Validate old password before setting new.
|
|
if password != decrypted_password:
|
|
return unauthorized(gettext("Incorrect password."))
|
|
|
|
# Hash new password before saving it.
|
|
if manager.sversion >= 100000:
|
|
password = conn.pq_encrypt_password_conn(data['newPassword'],
|
|
manager.user)
|
|
if password is None:
|
|
# Unable to encrypt the password so used the
|
|
# old method of encryption
|
|
password = pqencryptpassword(data['newPassword'],
|
|
manager.user)
|
|
else:
|
|
password = pqencryptpassword(data['newPassword'], manager.user)
|
|
|
|
SQL = render_template(
|
|
"/servers/sql/#{0}#/change_password.sql".format(
|
|
manager.version),
|
|
conn=conn, _=gettext,
|
|
user=manager.user, encrypted_password=password)
|
|
|
|
status, res = conn.execute_scalar(SQL)
|
|
|
|
if not status:
|
|
return internal_server_error(errormsg=res)
|
|
|
|
# Store password in sqlite only if no pgpass file
|
|
if not is_passfile:
|
|
password = encrypt(data['newPassword'], crypt_key)
|
|
# Check if old password was stored in pgadmin4 sqlite database.
|
|
# If yes then update that password.
|
|
if server.password is not None and config.ALLOW_SAVE_PASSWORD:
|
|
setattr(server, 'password', password)
|
|
db.session.commit()
|
|
# Also update password in connection manager.
|
|
manager.password = password
|
|
manager.update_session()
|
|
|
|
return make_json_response(
|
|
status=200,
|
|
success=1,
|
|
info=gettext(
|
|
"Password changed successfully."
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
def wal_replay(self, sid, pause=True):
|
|
"""
|
|
Utility function for wal_replay for resume/pause.
|
|
"""
|
|
server = Server.query.filter_by(
|
|
user_id=current_user.id, id=sid
|
|
).first()
|
|
|
|
if server is None:
|
|
return make_json_response(
|
|
success=0,
|
|
errormsg=self.not_found_error_msg()
|
|
)
|
|
|
|
try:
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
|
|
# Execute SQL to pause or resume WAL replay
|
|
if conn.connected():
|
|
if pause:
|
|
sql = "SELECT pg_xlog_replay_pause();"
|
|
if manager.version >= 100000:
|
|
sql = "SELECT pg_wal_replay_pause();"
|
|
|
|
status, res = conn.execute_scalar(sql)
|
|
if not status:
|
|
return internal_server_error(
|
|
errormsg=str(res)
|
|
)
|
|
else:
|
|
sql = "SELECT pg_xlog_replay_resume();"
|
|
if manager.version >= 100000:
|
|
sql = "SELECT pg_wal_replay_resume();"
|
|
|
|
status, res = conn.execute_scalar(sql)
|
|
if not status:
|
|
return internal_server_error(
|
|
errormsg=str(res)
|
|
)
|
|
return make_json_response(
|
|
success=1,
|
|
info=gettext('WAL replay paused'),
|
|
data={'in_recovery': True, 'wal_pause': pause}
|
|
)
|
|
return gone(errormsg=gettext('Please connect the server.'))
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
'WAL replay pause/resume failed'
|
|
)
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
def resume_wal_replay(self, gid, sid):
|
|
"""
|
|
This method will resume WAL replay
|
|
|
|
Args:
|
|
gid: Server group ID
|
|
sid: Server ID
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
return self.wal_replay(sid, False)
|
|
|
|
def pause_wal_replay(self, gid, sid):
|
|
"""
|
|
This method will pause WAL replay
|
|
|
|
Args:
|
|
gid: Server group ID
|
|
sid: Server ID
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
return self.wal_replay(sid, True)
|
|
|
|
def check_pgpass(self, gid, sid):
|
|
"""
|
|
This function is used to check whether server is connected
|
|
using pgpass file or not
|
|
|
|
Args:
|
|
gid: Group id
|
|
sid: Server id
|
|
"""
|
|
is_pgpass = False
|
|
server = Server.query.filter_by(
|
|
user_id=current_user.id, id=sid
|
|
).first()
|
|
|
|
if server is None:
|
|
return make_json_response(
|
|
success=0,
|
|
errormsg=self.not_found_error_msg()
|
|
)
|
|
|
|
try:
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
|
conn = manager.connection()
|
|
if not conn.connected():
|
|
return gone(
|
|
errormsg=gettext('Please connect the server.')
|
|
)
|
|
|
|
if (not server.password or not manager.password) and \
|
|
server.passfile and manager.passfile and \
|
|
server.passfile == manager.passfile:
|
|
is_pgpass = True
|
|
return make_json_response(
|
|
success=1,
|
|
data=dict({'is_pgpass': is_pgpass}),
|
|
)
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
'Cannot able to fetch pgpass status'
|
|
)
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
def get_response_for_password(self, server, status, prompt_password=False,
|
|
prompt_tunnel_password=False, errmsg=None):
|
|
|
|
if server.use_ssh_tunnel:
|
|
return make_json_response(
|
|
success=0,
|
|
status=status,
|
|
result=render_template(
|
|
'servers/tunnel_password.html',
|
|
server_label=server.name,
|
|
username=server.username,
|
|
tunnel_username=server.tunnel_username,
|
|
tunnel_host=server.tunnel_host,
|
|
tunnel_identity_file=server.tunnel_identity_file,
|
|
errmsg=errmsg,
|
|
_=gettext,
|
|
prompt_tunnel_password=prompt_tunnel_password,
|
|
prompt_password=prompt_password
|
|
)
|
|
)
|
|
else:
|
|
return make_json_response(
|
|
success=0,
|
|
status=status,
|
|
result=render_template(
|
|
'servers/password.html',
|
|
server_label=server.name,
|
|
username=server.username,
|
|
errmsg=errmsg,
|
|
_=gettext,
|
|
)
|
|
)
|
|
|
|
def clear_saved_password(self, gid, sid):
|
|
"""
|
|
This function is used to remove database server password stored into
|
|
the pgAdmin's db file.
|
|
|
|
:param gid:
|
|
:param sid:
|
|
:return:
|
|
"""
|
|
try:
|
|
server = Server.query.filter_by(
|
|
user_id=current_user.id, id=sid
|
|
).first()
|
|
|
|
if server is None:
|
|
return make_json_response(
|
|
success=0,
|
|
info=self.not_found_error_msg()
|
|
)
|
|
|
|
setattr(server, 'password', None)
|
|
# If password was saved then clear the flag also
|
|
# 0 is False in SQLite db
|
|
if server.save_password:
|
|
setattr(server, 'save_password', 0)
|
|
db.session.commit()
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Unable to clear saved password.\nError: {0}".format(str(e))
|
|
)
|
|
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
return make_json_response(
|
|
success=1,
|
|
info=gettext("The saved password cleared successfully."),
|
|
data={'is_password_saved': False}
|
|
)
|
|
|
|
def clear_sshtunnel_password(self, gid, sid):
|
|
"""
|
|
This function is used to remove sshtunnel password stored into
|
|
the pgAdmin's db file.
|
|
|
|
:param gid:
|
|
:param sid:
|
|
:return:
|
|
"""
|
|
try:
|
|
server = Server.query.filter_by(
|
|
user_id=current_user.id, id=sid
|
|
).first()
|
|
|
|
if server is None:
|
|
return make_json_response(
|
|
success=0,
|
|
info=self.not_found_error_msg()
|
|
)
|
|
|
|
setattr(server, 'tunnel_password', None)
|
|
db.session.commit()
|
|
except Exception as e:
|
|
current_app.logger.error(
|
|
"Unable to clear ssh tunnel password."
|
|
"\nError: {0}".format(str(e))
|
|
)
|
|
|
|
return internal_server_error(errormsg=str(e))
|
|
|
|
return make_json_response(
|
|
success=1,
|
|
info=gettext("The saved password cleared successfully."),
|
|
data={'is_tunnel_password_saved': False}
|
|
)
|
|
|
|
|
|
SchemaDiffRegistry(blueprint.node_type, ServerNode)
|
|
ServerNode.register_node_view(blueprint)
|