pgadmin4/web/pgadmin/browser/server_groups/servers/__init__.py
Ashesh Vashi c53b57a013 Resolved couple of small bugs introduced during database server
connection management implementation.
2015-10-28 22:34:25 +05:30

655 lines
21 KiB
Python

##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
from abc import ABCMeta, abstractmethod, abstractproperty
from flask import render_template, request, make_response, jsonify, current_app
from flask.ext.security import login_required, current_user
from pgadmin.settings.settings_model import db, Server, ServerGroup, User
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error, success_return, \
unauthorized, bad_request, precondition_required, forbidden
from pgadmin.browser.utils import NodeView
import traceback
from flask.ext.babel import gettext
import pgadmin.browser.server_groups as sg
from pgadmin.utils.crypto import encrypt, decrypt
from pgadmin.browser import BrowserPluginModule
from config import PG_DEFAULT_DRIVER
class ServerModule(sg.ServerGroupPluginModule):
NODE_TYPE = "server"
@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
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)
from pgadmin.utils.driver import get_driver
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
manager = driver.connection_manager(server.id)
conn = manager.connection()
module = getattr(manager, "module", None)
connected = conn.connected()
yield self.generate_browser_node(
"%d" % (server.id),
"%d" % gid,
server.name,
"icon-server-not-connected" if not connected else
"icon-{0}".format(module.NODE_TYPE),
True,
self.NODE_TYPE,
connected=connected,
server_type=module.type if module is not None else "PG"
)
@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)
return snippets
class ServerMenuItem(MenuItem):
def __init__(self, **kwargs):
kwargs.setdefault("type", ServerModule.NODE_TYPE)
super(ServerMenuItem, self).__init__(**kwargs)
blueprint = ServerModule(__name__)
class ServerTypeModule(BrowserPluginModule):
"""
Base class for different server types.
"""
__metaclass__ = ABCMeta
@abstractproperty
def type(self):
pass
@abstractproperty
def description(self):
pass
@abstractproperty
def priority(self):
pass
def get_nodes(self, manager=None, sid=None):
assert(sid is not None)
nodes = []
for module in self.submodules:
if isinstance(module, PGChildModule):
if manager and module.BackendSupported(manager):
nodes.extend(module.get_nodes(sid=sid, manager=manager))
else:
nodes.extend(module.get_nodes(sid=sid))
return nodes
@abstractmethod
def instanceOf(self, version):
pass
def __str__(self):
return "Type: {0},Description:{1}".format(self.type, self.description)
@property
def csssnippets(self):
"""
Returns a snippet of css to include in the page
"""
snippets = [
render_template(
"css/node.css",
node_type=self.node_type
)
]
for submodule in self.submodules:
snippets.extend(submodule.csssnippets)
return snippets
def get_own_javascripts(self):
scripts = []
for module in self.submodules:
scripts.extend(module.get_own_javascripts())
return scripts
@property
def script_load(self):
"""
Load the module script for all server types, when a server node is
initialized.
"""
return ServerTypeModule.NODE_TYPE
class ServerNode(NodeView):
node_type = ServerModule.NODE_TYPE
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': 'nodes'}],
'sql': [{'get': 'sql', 'post': 'modified_sql'}],
'stats': [{'get': 'statistics'}],
'deps': [{'get': 'dependencies', 'post': 'dependents'}],
'module.js': [{}, {}, {'get': 'module_js'}],
'connect': [{
'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'
}]
})
def list(self, gid):
res = []
"""Return a JSON document listing the server groups for the user"""
servers = Server.query.filter_by(user_id=current_user.id,
servergroup_id=gid)
from pgadmin.utils.driver import get_driver
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
manager = driver.connection_manager(server.id)
conn = manager.connection()
module = getattr(manager, "module", None)
connected = conn.connected()
res.append(
self.blueprint.generate_browser_node(
"%d" % (server.id),
"%d" % gid,
server.name,
"icon-server-not-connected" if not connected else
"icon-{0}".format(module.NODE_TYPE),
True,
self.node_type,
connected=connected,
server_type=module.type if module is not None else 'PG'
)
)
return make_json_response(result=res)
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, can not be deleted
if servers is None:
return make_json_response(
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:
db.session.delete(s)
db.session.commit()
except Exception as e:
return make_json_response(
success=0,
errormsg=e.message)
return make_json_response(success=1,
info=traceback.format_exc())
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(
success=0,
errormsg=gettext("Couldn't find the given server.")
)
# Not all parameters can be modified, while the server is connected
config_param_map = {
'name': 'name',
'host': 'host',
'port': 'port',
'db': 'maintenance_db',
'username': 'username',
'sslmode': 'sslmode',
'gid': 'servergroup_id',
'comment': 'comment',
'role': 'role'
}
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)
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
not_allowed = {}
if conn.connected():
for arg in {
'host', 'port', 'db', 'username', 'sslmode', 'role'
}:
if arg in data:
return forbidden(
errormsg=gettext(
"'{0}' is not allowed to modify, when server is connected."
).format(disp_lbl[arg])
)
for arg in config_param_map:
if arg in data:
setattr(server, config_param_map[arg], data[arg])
idx += 1
if idx == 0:
return make_json_response(
success=0,
errormsg=gettext('No parameters were changed!')
)
try:
db.session.commit()
except Exception as e:
return make_json_response(
success=0,
errormsg=e.message
)
manager.update(server)
return make_json_response(
success=1,
data={
'id': server.id,
'gid': server.servergroup_id,
'icon': 'icon-server-not-connected'
}
)
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(
success=0,
errormsg=gettext("Couldn't find the given server")
)
sg = ServerGroup.query.filter_by(
user_id=current_user.id,
id=server.servergroup_id
).first()
from pgadmin.utils.driver import get_driver
driver = get_driver(PG_DEFAULT_DRIVER)
manager = driver.connection_manager(sid)
conn = manager.connection()
connected = conn.connected()
module = getattr(manager, 'module', None)
return ajax_response(
response={
'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': module.type if module is not None else 'PG'
}
)
def create(self, gid):
"""Add a server node to the settings database"""
required_args = [
u'name',
u'host',
u'port',
u'db',
u'username',
u'sslmode',
u'role'
]
data = request.form if request.form else json.loads(request.data)
for arg in required_args:
if arg not in data:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
"Couldn't find the required parameter (%s)." % arg
)
)
try:
server = Server(
user_id=current_user.id,
servergroup_id=gid,
name=data[u'name'],
host=data[u'host'],
port=data[u'port'],
maintenance_db=data[u'db'],
username=data[u'username'],
ssl_mode=data[u'sslmode'],
comment=data[u'comment'] if u'comment' in data else None,
role=data[u'role'] if u'role' in data else None
)
db.session.add(server)
db.session.commit()
return jsonify(
node=self.blueprint.generate_browser_node(
"%d" % (server.id),
"%d" % gid,
server.name,
"icon-server-not-connected",
True,
self.node_type,
connected=False,
server_type='PG' # Default server type
)
)
except Exception as e:
return make_json_response(
status=410,
success=0,
errormsg=e.message
)
def nodes(self, gid, sid):
"""Build a list of treeview nodes from the child nodes."""
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
if not conn.connected():
return precondition_required(
gettext(
"Please make a connection to the server first!"
)
)
nodes = []
# We will rely on individual server type modules to generate nodes for
# them selves.
module = getattr(manager, 'module', None)
if module:
nodes.extend(module.get_nodes(sid=sid, manager=manager))
return make_json_response(data=nodes)
def sql(self, gid, sid):
return make_json_response(data='')
def modified_sql(self, gid, sid):
return make_json_response(data='')
def statistics(self, gid, sid):
return make_json_response(data='')
def dependencies(self, gid, sid):
return make_json_response(data='')
def dependents(self, gid, sid):
return make_json_response(data='')
def module_js(self, **kwargs):
"""
This property defines (if javascript) exists for this node.
Override this property for your own logic.
"""
return make_response(
render_template(
"servers/servers.js",
server_types=sorted(
[
m for m in self.blueprint.submodules
if isinstance(m, ServerTypeModule)
],
key=lambda x: x.priority
),
_=gettext
),
200, {'Content-Type': 'application/x-javascript'}
)
def connect_status(self, gid, sid):
"""Check and return the connection status."""
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
return make_json_response(data={'connected': conn.connected()})
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(gettext("Server Not Found."))
# Fetch User Details.
user = User.query.filter_by(id=current_user.id).first()
if user is None:
return unauthorized(gettext("Unauthorized Request."))
data = request.form if request.form else json.loads(request.data) if \
request.data else {}
password = None
save_password = False
if 'password' not in data:
if server.password is None:
# Return the password template in case password is not
# provided, or password has not been saved earlier.
return make_json_response(
success=0,
status=428,
result=render_template(
'servers/password.html',
server_label=server.name,
username=server.username,
_=gettext
)
)
else:
password = data['password'] if 'password' in data else None
save_password = \
data['save_password'] if password and \
'save_password' in data else False
# Encrypt the password before saving with user's login password key.
try:
password = encrypt(password, user.password) \
if password is not None else server.password
except Exception as e:
return internal_server_error(errormsg=e.message)
# Connect the Server
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
try:
status, errmsg = conn.connect(
password=password,
modules=[
m for m in self.blueprint.submodules
if isinstance(m, ServerTypeModule)
]
)
except Exception as e:
# TODO::
# Ask the password again (if existing password couldn't be
# descrypted)
return internal_server_error(errormsg=e.message)
if not status:
current_app.logger.error(
"Could not connected to server(#{0}) - '{1}'.\nError: {2}".format(
server.id, server.name, errmsg
)
)
return make_json_response(
success=0,
status=401,
result=render_template(
'servers/password.html',
server_label=server.name,
username=server.username,
errmsg=errmsg,
_=gettext
)
)
else:
if save_password:
try:
# Save the encrypted password using the user's login
# password key.
setattr(server, 'password', password)
db.session.commit()
except Exception as e:
# Release Connection
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))
return make_json_response(
success=1,
info=gettext("Server Connected."),
data={
'icon': 'icon-{0}'.format(
manager.module.NODE_TYPE
),
'connected': True
}
)
def disconnect(self, gid, sid):
"""Disconnect the Server."""
server = Server.query.filter_by(id=sid).first()
if server is None:
return bad_request(gettext("Server Not Found."))
# Release Connection
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
status = manager.release()
if not status:
return unauthorized(gettext("Server Could Not Disconnect."))
else:
return make_json_response(
success=1,
info=gettext("Server Disconnected."),
data={
'icon': 'icon-server-not-connected',
'connected': False
}
)
ServerNode.register_node_view(blueprint)