diff --git a/web/migrations/versions/a091c9611d20_.py b/web/migrations/versions/a091c9611d20_.py
new file mode 100644
index 000000000..d9da2b10f
--- /dev/null
+++ b/web/migrations/versions/a091c9611d20_.py
@@ -0,0 +1,72 @@
+
+"""empty message
+
+Revision ID: a091c9611d20
+Revises: 84700139beb0
+Create Date: 2020-07-14 17:20:22.705737
+
+"""
+from pgadmin.model import db
+
+
+# revision identifiers, used by Alembic.
+revision = 'a091c9611d20'
+down_revision = '84700139beb0'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ db.engine.execute(
+ 'ALTER TABLE server ADD COLUMN shared BOOLEAN'
+ )
+
+ db.engine.execute("""
+ CREATE TABLE sharedserver (
+ id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ server_owner VARCHAR(64),
+ servergroup_id INTEGER NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ host VARCHAR(128),
+ port INTEGER NOT NULL CHECK(port >= 1 AND port <= 65534),
+ maintenance_db VARCHAR(64),
+ username VARCHAR(64),
+ password VARCHAR(64),
+ role VARCHAR(64),
+ ssl_mode VARCHAR(16) NOT NULL CHECK(ssl_mode IN
+ ( 'allow' , 'prefer' , 'require' , 'disable' ,
+ 'verify-ca' , 'verify-full' )
+ ),
+ comment VARCHAR(1024),
+ discovery_id VARCHAR(128),
+ hostaddr TEXT(1024),
+ db_res TEXT,
+ passfile TEXT,
+ sslcert TEXT,
+ sslkey TEXT,
+ sslrootcert TEXT,
+ sslcrl TEXT,
+ sslcompression INTEGER DEFAULT 0,
+ bgcolor TEXT(10),
+ fgcolor TEXT(10),
+ service TEXT,
+ use_ssh_tunnel INTEGER DEFAULT 0,
+ tunnel_host TEXT,
+ tunnel_port TEXT,
+ tunnel_username TEXT,
+ tunnel_authentication INTEGER DEFAULT 0,
+ tunnel_identity_file TEXT,
+ shared BOOLEAN NOT NULL,
+ save_password BOOLEAN NOT NULL,
+ tunnel_password VARCHAR(64),
+ connect_timeout INTEGER ,
+ PRIMARY KEY(id),
+ FOREIGN KEY(user_id) REFERENCES user(id),
+ FOREIGN KEY(servergroup_id) REFERENCES servergroup(id)
+ );
+ """)
+
+
+def downgrade():
+ pass
diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py
index 948be41b8..215421cbf 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -9,6 +9,7 @@
from flask_babelex import gettext
from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
PREF_LABEL_KEYBOARD_SHORTCUTS
+import config
LOCK_LAYOUT_LEVEL = {
'PREVENT_DOCKING': 'docking',
@@ -23,6 +24,15 @@ def register_browser_preferences(self):
gettext("Show system objects?"), 'boolean', False,
category_label=PREF_LABEL_DISPLAY
)
+ if config.SERVER_MODE:
+ self.hide_shared_server = self.preference.register(
+ 'display', 'hide_shared_server',
+ gettext("Hide shared server?"), 'boolean', False,
+ category_label=gettext('Display'),
+ help_str=gettext(
+ 'If set to true, then all shared server will be hidden'
+ )
+ )
self.preference.register(
'display', 'enable_acitree_animation',
diff --git a/web/pgadmin/browser/server_groups/__init__.py b/web/pgadmin/browser/server_groups/__init__.py
index 3c4ec6831..bf5be6c84 100644
--- a/web/pgadmin/browser/server_groups/__init__.py
+++ b/web/pgadmin/browser/server_groups/__init__.py
@@ -13,7 +13,7 @@ import simplejson as json
from abc import ABCMeta, abstractmethod
import six
-from flask import request, jsonify
+from flask import request, jsonify, render_template
from flask_babelex import gettext
from flask_security import current_user, login_required
from pgadmin.browser import BrowserPluginModule
@@ -22,7 +22,27 @@ from pgadmin.utils.ajax import make_json_response, gone, \
make_response as ajax_response, bad_request
from pgadmin.utils.menu import MenuItem
from sqlalchemy import exc
-from pgadmin.model import db, ServerGroup
+from pgadmin.model import db, ServerGroup, Server
+import config
+from pgadmin.utils.preferences import Preferences
+
+
+def get_icon_css_class(group_id, group_user_id,
+ default_val='icon-server_group'):
+ """
+ Returns css value
+ :param group_id:
+ :param group_user_id:
+ :param default_val:
+ :return: default_val
+ """
+ if (config.SERVER_MODE and
+ group_user_id != current_user.id and
+ ServerGroupModule.has_shared_server(group_id)):
+ default_val = 'icon-server_group_shared'
+
+ return default_val
+
SG_NOT_FOUND_ERROR = 'The specified server group could not be found.'
@@ -31,19 +51,63 @@ class ServerGroupModule(BrowserPluginModule):
_NODE_TYPE = "server_group"
node_icon = "icon-%s" % _NODE_TYPE
+ @property
+ def csssnippets(self):
+ """
+ Returns a snippet of css to include in the page
+ """
+ snippets = [render_template("css/server_group.css")]
+
+ for submodule in self.submodules:
+ snippets.extend(submodule.csssnippets)
+
+ return snippets
+
+ @staticmethod
+ def has_shared_server(gid):
+ """
+ To check whether given server group contains shared server or not
+ :param gid:
+ :return: True if servergroup contains shared server else false
+ """
+ servers = Server.query.filter_by(servergroup_id=gid)
+ for s in servers:
+ if s.shared:
+ return True
+ return False
+
def get_nodes(self, *arg, **kwargs):
"""Return a JSON document listing the server groups for the user"""
- groups = ServerGroup.query.filter_by(
- user_id=current_user.id
- ).order_by("id")
+
+ hide_shared_server = None
+ if config.SERVER_MODE:
+ pref = Preferences.module('browser')
+ hide_shared_server = pref.preference('hide_shared_server').get()
+
+ if config.SERVER_MODE:
+ server_groups = ServerGroup.query.all()
+ groups = []
+ for group in server_groups:
+ if hide_shared_server and self.has_shared_server(
+ group.id) and group.user_id != current_user.id:
+ continue
+ if group.user_id == current_user.id or \
+ self.has_shared_server(group.id):
+ groups.append(group)
+ else:
+ groups = ServerGroup.query.filter_by(
+ user_id=current_user.id
+ ).order_by("id")
+
for idx, group in enumerate(groups):
yield self.generate_browser_node(
"%d" % (group.id), None,
group.name,
- self.node_icon,
+ get_icon_css_class(group.id, group.user_id),
True,
self.node_type,
- can_delete=True if idx > 0 else False
+ can_delete=True if idx > 0 else False,
+ user_id=group.user_id
)
@property
@@ -196,7 +260,7 @@ class ServerGroupView(NodeView):
gid,
None,
servergroup.name,
- self.node_icon,
+ get_icon_css_class(gid, servergroup.user_id),
True,
self.node_type,
can_delete=True # This is user created hence can deleted
@@ -207,10 +271,7 @@ class ServerGroupView(NodeView):
def properties(self, gid):
"""Update the server-group properties"""
- # There can be only one record at most
- sg = ServerGroup.query.filter_by(
- user_id=current_user.id,
- id=gid).first()
+ sg = ServerGroup.query.filter(ServerGroup.id == gid).first()
if sg is None:
return make_json_response(
@@ -220,7 +281,7 @@ class ServerGroupView(NodeView):
)
else:
return ajax_response(
- response={'id': sg.id, 'name': sg.name},
+ response={'id': sg.id, 'name': sg.name, 'user_id': sg.user_id},
status=200
)
@@ -246,7 +307,7 @@ class ServerGroupView(NodeView):
"%d" % sg.id,
None,
sg.name,
- self.node_icon,
+ get_icon_css_class(sg.id, sg.user_id),
True,
self.node_type,
# This is user created hence can deleted
@@ -306,14 +367,14 @@ class ServerGroupView(NodeView):
"%d" % group.id,
None,
group.name,
- self.node_icon,
+ get_icon_css_class(group.id, group.user_id),
True,
self.node_type
)
)
else:
- group = ServerGroup.query.filter_by(user_id=current_user.id,
- id=gid).first()
+ group = ServerGroup.query.filter(ServerGroup.id == gid).first()
+
if not group:
return gone(
errormsg=gettext("Could not find the server group.")
@@ -322,7 +383,7 @@ class ServerGroupView(NodeView):
nodes = self.blueprint.generate_browser_node(
"%d" % (group.id), None,
group.name,
- self.node_icon,
+ get_icon_css_class(group.id, group.user_id),
True,
self.node_type
)
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py
index 98bb1974d..714b4c9e0 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -23,7 +23,7 @@ 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.model import db, Server, ServerGroup, User, SharedServer
from pgadmin.utils.driver import get_driver
from pgadmin.utils.master_password import get_crypt_key
from pgadmin.utils.exception import CryptKeyMissing
@@ -31,6 +31,8 @@ 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
+from sqlalchemy import or_
+from pgadmin.utils.preferences import Preferences
def has_any(data, keys):
@@ -91,6 +93,10 @@ def server_icon_and_background(is_connected, manager, server):
return 'icon-{0}{1}'.format(
manager.server_type, server_background_color
)
+ elif server.shared and config.SERVER_MODE:
+ return 'icon-shared-server-not-connected{0}'.format(
+ server_background_color
+ )
else:
return 'icon-server-not-connected{0}'.format(
server_background_color
@@ -113,15 +119,55 @@ class ServerModule(sg.ServerGroupPluginModule):
"""
return sg.ServerGroupModule.node_type
+ @staticmethod
+ def get_shared_server_properties(server, sharedserver):
+ """
+ Return shared server properties
+ :param server:
+ :param sharedserver:
+ :return:
+ """
+
+ server.bgcolor = sharedserver.bgcolor
+ server.fgcolor = sharedserver.fgcolor
+ server.name = sharedserver.name
+ server.role = sharedserver.role
+ server.tunnel_username = sharedserver.tunnel_username
+ server.tunnel_password = sharedserver.tunnel_password
+ server.save_password = sharedserver.save_password
+ server.passfile = sharedserver.passfile
+ server.servergroup_id = sharedserver.servergroup_id
+ server.sslcert = sharedserver.sslcert
+ server.username = sharedserver.username
+ server.server_owner = sharedserver.server_owner
+
+ return server
+
@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)
+
+ hide_shared_server = None
+ if config.SERVER_MODE:
+ pref = Preferences.module('browser')
+ hide_shared_server = pref.preference('hide_shared_server').get()
+
+ servers = Server.query.filter(
+ or_(Server.user_id == current_user.id, Server.shared),
+ Server.servergroup_id == gid)
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
+ if server.shared and server.user_id != current_user.id:
+ # Don't include shared server if hide shared server is
+ # set to true
+ if hide_shared_server:
+ continue
+ shared_server = self.get_shared_server(server, gid)
+ server = self.get_shared_server_properties(server,
+ shared_server)
+
connected = False
manager = None
errmsg = None
@@ -157,7 +203,10 @@ class ServerModule(sg.ServerGroupPluginModule):
is_tunnel_password_saved=True
if server.tunnel_password is not None else False,
was_connected=was_connected,
- errmsg=errmsg
+ errmsg=errmsg,
+ user_id=server.user_id,
+ user_name=server.username,
+ shared=server.shared
)
@property
@@ -229,6 +278,79 @@ class ServerModule(sg.ServerGroupPluginModule):
def get_exposed_url_endpoints(self):
return ['NODE-server.connect_id']
+ @staticmethod
+ def create_shared_server(data, gid):
+ """
+ Create shared server
+ :param data:
+ :param gid:
+ :return: None
+ """
+
+ shared_server = None
+ try:
+ user = User.query.filter_by(id=data.user_id).first()
+ shared_server = SharedServer(
+ user_id=current_user.id,
+ server_owner=user.username,
+ servergroup_id=gid,
+ name=data.name,
+ host=data.host,
+ hostaddr=data.hostaddr,
+ port=data.port,
+ maintenance_db=None,
+ username=None,
+ save_password=0,
+ ssl_mode=data.ssl_mode,
+ comment=None,
+ role=data.role,
+ sslcert=None,
+ sslkey=None,
+ sslrootcert=None,
+ sslcrl=None,
+ bgcolor=data.bgcolor if data.bgcolor else None,
+ fgcolor=data.fgcolor if data.fgcolor else None,
+ service=data.service if data.service else None,
+ connect_timeout=0,
+ use_ssh_tunnel=0,
+ tunnel_host=None,
+ tunnel_port=22,
+ tunnel_username=None,
+ tunnel_authentication=0,
+ tunnel_identity_file=None,
+ shared=data.shared if data.shared else None
+ )
+ db.session.add(shared_server)
+ db.session.commit()
+ except Exception as e:
+ if shared_server:
+ db.session.delete(shared_server)
+ db.session.commit()
+
+ current_app.logger.exception(e)
+ return internal_server_error(errormsg=str(e))
+
+ @staticmethod
+ def get_shared_server(server, gid):
+ """
+ return the shared server
+ :param server:
+ :param gid:
+ :return: shared_server
+ """
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=gid).first()
+
+ if shared_server is None:
+ ServerModule.create_shared_server(server, gid)
+
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=gid).first()
+
+ return shared_server
+
class ServerMenuItem(MenuItem):
def __init__(self, **kwargs):
@@ -326,12 +448,19 @@ class ServerNode(PGChildNodeView):
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)
+ servers = Server.query.filter(
+ or_(Server.user_id == current_user.id,
+ Server.shared),
+ Server.servergroup_id == gid)
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
+ if server.shared and server.user_id != current_user.id:
+ shared_server = ServerModule.get_shared_server(server, gid)
+ server = \
+ ServerModule.get_shared_server_properties(server,
+ shared_server)
manager = driver.connection_manager(server.id)
conn = manager.connection()
connected = conn.connected()
@@ -364,7 +493,9 @@ class ServerNode(PGChildNodeView):
is_password_saved=bool(server.save_password),
is_tunnel_password_saved=True
if server.tunnel_password is not None else False,
- errmsg=errmsg
+ errmsg=errmsg,
+ user_name=server.username,
+ shared=server.shared
)
)
@@ -378,9 +509,7 @@ class ServerNode(PGChildNodeView):
@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()
+ server = Server.query.filter_by(id=sid).first()
if server is None:
return make_json_response(
@@ -425,14 +554,36 @@ class ServerNode(PGChildNodeView):
is_password_saved=bool(server.save_password),
is_tunnel_password_saved=True
if server.tunnel_password is not None else False,
- errmsg=errmsg
+ errmsg=errmsg,
+ shared=server.shared
),
)
+ def delete_shared_server(self, server_name, gid):
+ """
+ Delete the shared server
+ :param server_name:
+ :return:
+ """
+ try:
+ shared_server = SharedServer.query.filter_by(name=server_name,
+ servergroup_id=gid)
+ for s in shared_server:
+ get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id)
+ db.session.delete(s)
+ db.session.commit()
+
+ except Exception as e:
+ current_app.logger.exception(e)
+ return make_json_response(
+ success=0,
+ errormsg=e.message)
+
@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)
+ server_name = None
# TODO:: A server, which is connected, cannot be deleted
if servers is None:
@@ -448,10 +599,11 @@ class ServerNode(PGChildNodeView):
else:
try:
for s in servers:
+ server_name = s.name
get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id)
db.session.delete(s)
db.session.commit()
-
+ self.delete_shared_server(server_name, gid)
QueryHistory.clear_history(current_user.id, sid)
except Exception as e:
@@ -466,8 +618,8 @@ class ServerNode(PGChildNodeView):
@login_required
def update(self, gid, sid):
"""Update the server settings"""
- server = Server.query.filter_by(
- user_id=current_user.id, id=sid).first()
+ server = Server.query.filter_by(id=sid).first()
+ sharedserver = None
if server is None:
return make_json_response(
@@ -476,6 +628,10 @@ class ServerNode(PGChildNodeView):
errormsg=gettext("Could not find the required server.")
)
+ if config.SERVER_MODE and server.shared and \
+ server.user_id != current_user.id:
+ sharedserver = ServerModule.get_shared_server(server, gid)
+
# Not all parameters can be modified, while the server is connected
config_param_map = {
'name': 'name',
@@ -505,11 +661,12 @@ class ServerNode(PGChildNodeView):
'tunnel_username': 'tunnel_username',
'tunnel_authentication': 'tunnel_authentication',
'tunnel_identity_file': 'tunnel_identity_file',
+ 'shared': 'shared'
}
disp_lbl = {
'name': gettext('name'),
- 'host': gettext('Host name/address'),
+ 'hostaddr': gettext('Host name/address'),
'port': gettext('Port'),
'db': gettext('Maintenance database'),
'username': gettext('Username'),
@@ -540,7 +697,8 @@ class ServerNode(PGChildNodeView):
self._server_modify_disallowed_when_connected(
connected, data, disp_lbl)
- idx = self._set_valid_attr_value(data, config_param_map, server)
+ idx = self._set_valid_attr_value(data, config_param_map, server,
+ sharedserver)
if idx == 0:
return make_json_response(
@@ -567,7 +725,7 @@ class ServerNode(PGChildNodeView):
node=self.blueprint.generate_browser_node(
"%d" % (server.id), server.servergroup_id,
server.name,
- server_icon_and_background(connected, manager, server),
+ server_icon_and_background(connected, manager, sharedserver) if server.shared and server.user_id != current_user.id else server_icon_and_background(connected, manager, server),
True,
self.node_type,
connected=connected,
@@ -576,7 +734,7 @@ class ServerNode(PGChildNodeView):
)
)
- def _set_valid_attr_value(self, data, config_param_map, server):
+ def _set_valid_attr_value(self, data, config_param_map, server, sharedserver):
idx = 0
for arg in config_param_map:
@@ -586,7 +744,11 @@ class ServerNode(PGChildNodeView):
# it manually to integer
if arg == 'sslcompression':
value = 1 if value else 0
- setattr(server, config_param_map[arg], value)
+ # setattr(server, config_param_map[arg], value)
+ if server.shared and server.user_id != current_user.id:
+ setattr(sharedserver, config_param_map[arg], value)
+ else:
+ setattr(server, config_param_map[arg], value)
idx += 1
return idx
@@ -612,11 +774,11 @@ class ServerNode(PGChildNodeView):
"""
Return list of attributes of all servers.
"""
- servers = Server.query.filter_by(
- user_id=current_user.id,
- servergroup_id=gid).order_by(Server.name)
+ servers = Server.query.filter(
+ or_(Server.user_id == current_user.id,
+ Server.shared),
+ Server.servergroup_id == gid).order_by(Server.name)
sg = ServerGroup.query.filter_by(
- user_id=current_user.id,
id=gid
).first()
res = []
@@ -653,7 +815,6 @@ class ServerNode(PGChildNodeView):
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:
@@ -662,9 +823,7 @@ class ServerNode(PGChildNodeView):
success=0,
errormsg=self.not_found_error_msg()
)
-
sg = ServerGroup.query.filter_by(
- user_id=current_user.id,
id=server.servergroup_id
).first()
@@ -676,50 +835,61 @@ class ServerNode(PGChildNodeView):
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
- }
- )
+ # if server.shared and not current_user.has_role("Administrator"):
+ if server.shared and server.user_id != current_user.id:
+ shared_server = ServerModule.get_shared_server(server, gid)
+ server = ServerModule.get_shared_server_properties(server,
+ shared_server)
+
+ response = {
+ 'id': server.id,
+ 'name': server.name,
+ 'server_owner': server.server_owner if (
+ server.shared and not current_user.has_role(
+ "Administrator")) else None,
+ 'user_id': server.user_id,
+ 'host': server.host,
+ 'hostaddr': server.hostaddr,
+ 'port': server.port,
+ 'db': server.maintenance_db,
+ 'shared': server.shared if config.SERVER_MODE else None,
+ '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
+ }
+
+ return ajax_response(response)
@login_required
def create(self, gid):
@@ -801,11 +971,11 @@ class ServerNode(PGChildNodeView):
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)
+ tunnel_identity_file=data.get('tunnel_identity_file', None),
+ shared=data.get('shared', None)
)
db.session.add(server)
db.session.commit()
-
connected = False
user = None
manager = None
@@ -1005,6 +1175,11 @@ class ServerNode(PGChildNodeView):
# Fetch Server Details
server = Server.query.filter_by(id=sid).first()
+ shared_server = None
+ if server.shared and server.user_id != current_user.id:
+ shared_server = ServerModule.get_shared_server(server, gid)
+ server = ServerModule.get_shared_server_properties(server,
+ shared_server)
if server is None:
return bad_request(self.not_found_error_msg())
@@ -1062,7 +1237,6 @@ class ServerNode(PGChildNodeView):
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 \
@@ -1124,10 +1298,18 @@ class ServerNode(PGChildNodeView):
# every time user try to connect
# 1 is True in SQLite as no boolean type
setattr(server, 'save_password', 1)
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'save_password', 1)
+ else:
+ 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)
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'password', password)
+ else:
+ setattr(server, 'password', password)
db.session.commit()
except Exception as e:
# Release Connection
@@ -1561,21 +1743,39 @@ class ServerNode(PGChildNodeView):
:return:
"""
try:
- server = Server.query.filter_by(
- user_id=current_user.id, id=sid
- ).first()
-
+ server = Server.query.filter_by(id=sid).first()
+ shared_server = None
if server is None:
return make_json_response(
success=0,
info=self.not_found_error_msg()
)
- setattr(server, 'password', None)
+ if server.shared and server.user_id != current_user.id:
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=gid).first()
+
+ if shared_server is None:
+ return make_json_response(
+ success=0,
+ info=gettext("Could not find the required server.")
+ )
+ server = ServerModule. \
+ get_shared_server_properties(server, shared_server)
+
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'save_password', None)
+ else:
+ setattr(server, 'save_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)
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'save_password', 0)
+ else:
+ setattr(server, 'save_password', 0)
db.session.commit()
except Exception as e:
current_app.logger.error(
diff --git a/web/pgadmin/browser/server_groups/servers/static/img/sharedserverbad.svg b/web/pgadmin/browser/server_groups/servers/static/img/sharedserverbad.svg
new file mode 100644
index 000000000..4455089ac
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/static/img/sharedserverbad.svg
@@ -0,0 +1,45 @@
+
+
+
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js
index 7c1b58be8..a5ef532c6 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -56,7 +56,12 @@ define('pgadmin.node.server', [
type: 'server',
dialogHelp: url_for('help.static', {'filename': 'server_dialog.html'}),
label: gettext('Server'),
- canDrop: true,
+ canDrop: function(node){
+ var serverOwner = node.user_id;
+ if (serverOwner != current_user.id)
+ return false;
+ return true;
+ },
dropAsRemove: true,
dropPriority: 5,
hasStatistics: true,
@@ -75,12 +80,12 @@ define('pgadmin.node.server', [
name: 'create_server_on_sg', node: 'server_group', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 1, label: gettext('Server...'),
- data: {action: 'create'}, icon: 'wcTabIcon icon-server',
+ data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
},{
name: 'create_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 3, label: gettext('Server...'),
- data: {action: 'create'}, icon: 'wcTabIcon icon-server',
+ data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
},{
name: 'connect_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'connect_server',
@@ -150,6 +155,13 @@ define('pgadmin.node.server', [
is_not_connected: function(node) {
return (node && node.connected != true);
},
+ canCreate: function(node){
+ var serverOwner = node.user_id;
+ if (serverOwner == current_user.id || _.isUndefined(serverOwner))
+ return true;
+ return false;
+
+ },
is_connected: function(node) {
return (node && node.connected == true);
},
@@ -226,28 +238,25 @@ define('pgadmin.node.server', [
d = t.itemData(i);
t.removeIcon(i);
d.connected = false;
- d.icon = 'icon-server-not-connected';
+ if (d.shared){
+ d.icon = 'icon-shared-server-not-connected';
+ }else{
+ d.icon = 'icon-server-not-connected';
+ }
t.addIcon(i, {icon: d.icon});
obj.callbacks.refresh.apply(obj, [null, i]);
if (pgBrowser.serverInfo && d._id in pgBrowser.serverInfo) {
delete pgBrowser.serverInfo[d._id];
}
- pgBrowser.enable_disable_menus(i);
- // Trigger server disconnect event
- pgBrowser.Events.trigger(
- 'pgadmin:server:disconnect',
- {item: i, data: d}, false
- );
- }
- else {
- try {
- Alertify.error(res.errormsg);
- } catch (e) {
- console.warn(e.stack || e);
+ else {
+ try {
+ Alertify.error(res.errormsg);
+ } catch (e) {
+ console.warn(e.stack || e);
+ }
+ t.unload(i);
}
- t.unload(i);
- }
- })
+ }})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
t.unload(i);
@@ -745,12 +754,21 @@ define('pgadmin.node.server', [
id: 'id', label: gettext('ID'), type: 'int', mode: ['properties'],
},{
id: 'name', label: gettext('Name'), type: 'text',
- mode: ['properties', 'edit', 'create'],
- },{
+ mode: ['properties', 'edit', 'create'], disabled: function(model){
+ if (model.attributes.shared)
+ return true;
+ return false;
+ },
+ },
+ {
id: 'gid', label: gettext('Server group'), type: 'int',
control: 'node-list-by-id', node: 'server_group',
- mode: ['create', 'edit'], select2: {allowClear: false},
- },{
+ mode: ['create', 'edit'], select2: {allowClear: false}, visible: 'isVisible',
+ },
+ {
+ id: 'server_owner', label: gettext('Shared Server Owner'), type: 'text', mode: ['properties'],
+ },
+ {
id: 'server_type', label: gettext('Server type'), type: 'options',
mode: ['properties'], visible: 'isConnected',
'options': supported_servers,
@@ -773,6 +791,22 @@ define('pgadmin.node.server', [
id: 'connect_now', controlLabel: gettext('Connect now?'), type: 'checkbox',
group: null, mode: ['create'],
},{
+ id: 'shared', label: gettext('Shared with all?'), type: 'switch',
+ mode: ['properties', 'create', 'edit'], 'options': {'size': 'mini'},
+ readonly: function(model){
+ var serverOwner = model.attributes.user_id;
+ if (!model.isNew() && serverOwner != current_user.id){
+ return true;
+ }
+ return false;
+ },visible: function(){
+ if (current_user.is_admin && pgAdmin.server_mode == 'True')
+ return true;
+
+ return false;
+ },
+ },
+ {
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'],
},{
@@ -1057,7 +1091,23 @@ define('pgadmin.node.server', [
mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
min: 0,
}],
+ isVisible: function(model){
+ var serverOwner = model.attributes.user_id;
+ if (!model.isNew() && serverOwner != current_user.id){
+ return false;
+ }
+ return true;
+
+ },
validate: function() {
+ var msg;
+ this.errorModel.clear();
+ if (!this.isNew() && this.attributes.shared && this.attributes.user_id != current_user.id &&(_.isNull(this.get('username')))) {
+ msg = gettext('Username is not set,Please set the username.');
+ this.errorModel.set('username', msg);
+ return msg;
+ }
+
const validateModel = new modelValidation.ModelValidation(this);
return validateModel.validate();
},
@@ -1153,7 +1203,18 @@ define('pgadmin.node.server', [
}
},
});
+
var connect_to_server = function(obj, data, tree, item, reconnect) {
+ // Open properties dialog in edit mode
+ const selectedTreeNode = tree.selected().length > 0 ? tree.selected() : tree.first();
+ const selectedTreeNodeData = selectedTreeNode && selectedTreeNode.length === 1 ? tree.itemData(selectedTreeNode) : undefined;
+ if (data.shared && _.isNull(data.user_name) && data.user_id != current_user.id){
+ if (selectedTreeNodeData._type == 'server')
+ pgAdmin.Browser.Node.callbacks.show_obj_properties.call(
+ pgAdmin.Browser.Nodes[tree.itemData(selectedTreeNode)._type], {action: 'edit'}
+ );
+ return;
+ }
var wasConnected = reconnect || data.connected,
onFailure = function(
xhr, status, error, _node, _data, _tree, _item, _wasConnected
@@ -1164,7 +1225,12 @@ define('pgadmin.node.server', [
// Let's not change the status of the tree node now.
if (!_wasConnected) {
tree.setInode(_item);
- tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ if (data.shared){
+ tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ }
+
}
Alertify.pgNotifier('error', xhr, error, function(msg) {
@@ -1312,7 +1378,11 @@ define('pgadmin.node.server', [
_tree.unload(_item);
_tree.setInode(_item);
_tree.removeIcon(_item);
- _tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ if (_data.shared){
+ _tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ _tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ }
obj.trigger('connect:cancelled', data._id, data.db, obj, _item, _data);
pgBrowser.Events.trigger(
'pgadmin:server:connect:cancelled', data._id, _item, _data, obj
@@ -1378,7 +1448,11 @@ define('pgadmin.node.server', [
})
.fail(function(xhr, status, error) {
tree.setInode(item);
- tree.addIcon(item, {icon: 'icon-server-not-connected'});
+ if (data.shared){
+ tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ tree.addIcon(item, {icon: 'icon-server-not-connected'});
+ }
Alertify.pgRespErrorNotify(xhr, error);
});
};
diff --git a/web/pgadmin/browser/server_groups/servers/templates/css/servers.css b/web/pgadmin/browser/server_groups/servers/templates/css/servers.css
index 93c4536d4..a178f4fe5 100644
--- a/web/pgadmin/browser/server_groups/servers/templates/css/servers.css
+++ b/web/pgadmin/browser/server_groups/servers/templates/css/servers.css
@@ -15,3 +15,12 @@
vertical-align: middle;
height: 1.3em;
}
+
+.icon-shared-server-not-connected {
+ background-image: url('{{ url_for('NODE-server.static', filename='img/sharedserverbad.svg') }}') !important;
+ background-repeat: no-repeat;
+ background-size: 20px !important;
+ align-content: center;
+ vertical-align: middle;
+ height: 1.3em;
+}
diff --git a/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
new file mode 100644
index 000000000..e9eb73ce0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
@@ -0,0 +1,863 @@
+{
+ "add_server": [
+ {
+ "name": "Add server with service id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Test default server url",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server with connect timeout",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "connect_timeout": 5
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with password",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": true,
+ "save_password": false,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 1,
+ "tunnel_identity_file": "pkey_rsa"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with identity file",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": false,
+ "save_password": false,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 1,
+ "tunnel_identity_file": "pkey_rsa"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with password and saved it",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": true,
+ "save_password": true,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 0,
+ "tunnel_password": "123456"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with identity file and save the password",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": false,
+ "save_password": true,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 1,
+ "tunnel_identity_file": "pkey_rsa",
+ "tunnel_password": "123456"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server with password and save password to true",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "with_pwd": true,
+ "with_save": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server with password and save password to false",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "with_pwd": true,
+ "with_save": false,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server without password and save password to true",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "with_pwd": false,
+ "with_save": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server with connect now",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "is_password_saved": [
+ {
+ "name": "Connect server with 'save password",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "test_data": {
+ "is_password_saved": true
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200,
+ "message": "Server connected."
+ }
+ }
+ ],
+ "get_server": [
+ {
+ "name": "Get a server URL",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Reload a server configuration",
+ "url": "/browser/server/reload/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server URL using wrong server id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "incorrect_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Get a server Node dependants",
+ "url": "/browser/server/dependent/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node dependency",
+ "url": "/browser/server/dependency/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node sql",
+ "url": "/browser/server/sql/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node msql",
+ "url": "/browser/server/msql/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node statistics",
+ "url": "/browser/server/stats/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server pgpass details",
+ "url": "/browser/server/check_pgpass/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ }
+ ],
+ "get_shared_server": [
+ {
+ "name": "Get a shared server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "shared": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a all shared server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "shared": true,
+ "no_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get the all available shared server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "no_server_id": true,
+ "shared": true,
+ "server_list": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "get_all_server": [
+ {
+ "name": "Get the all children of server",
+ "url": "/browser/server/children/",
+ "is_positive_test": true,
+ "children": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 500
+ }
+ },
+ {
+ "name": "Get the all available servers",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "invalid_server_group": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Get the all available server of server group",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "server_list": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get the all available server of server group",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "server_list": true,
+ "servers": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get the all connected servers",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "server_list": true,
+ "servers": true,
+ "connected": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "connect_server": [
+ {
+ "name": "Get a server connection",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "connect to a server using password",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "connect": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Disconnect server test",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "disconnect": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Disconnect server when wrong server id passed",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "disconnect": true,
+ "wrong_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 400
+ }
+ },
+ {
+ "name": "Reload a server configuration",
+ "url": "/browser/server/reload/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Error while creating server restore point",
+ "url": "/browser/server/restore_point/",
+ "is_positive_test": true,
+ "restore_point": true,
+ "test_data": {
+ "Named restore point created": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 500
+ }
+ }
+ ],
+ "delete_server": [
+ {
+ "name": "Delete a server URL",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Disconnect server test",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Error while fetching a server to delete",
+ "url": "/browser/server/obj/",
+ "is_positive_test": false,
+ "mocking_required": true,
+ "mock_data": {
+ "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+ "return_value": "(True, 'Mocked Internal Server Error')"
+ },
+ "expected_data": {
+ "status_code": 500
+ }
+ },
+ {
+ "name": "server not found while deleting a server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "invalid_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ }
+ ],
+ "update_server": [
+ {
+ "name": "update a server name",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "sslcompression": 1
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "update a server details without data",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {},
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Update server with wrong server id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Update server with incorrect hostaddr",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "hostaddr": "PLACE_HOLDER",
+ "db_res": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 400
+ }
+ },
+ {
+ "name": "update a server , make server shared",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "shared": true
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password with wrong server id",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "wal replay",
+ "url": "/browser/server/wal_replay/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password with wrong server id",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "error while clearing a ssh password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "error_clearing_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "update_shared_server": [
+ {
+ "name": "update a server name",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "sslcompression": 1
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "update a server details without data",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {},
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Update server with wrong server id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Update server with incorrect hostaddr",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "hostaddr": "PLACE_HOLDER",
+ "db_res": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 400
+ }
+ },
+ {
+ "name": "update a server , make server shared",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "shared": true
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password when login user is not owner of server",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password with wrong server id",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password with wrong server id",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "error while clearing a ssh password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "error_clearing_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "delete_multiple_server": [
+ {
+ "name": "Delete multiple server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ]
+}
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
new file mode 100644
index 000000000..1b23fcdb8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
@@ -0,0 +1,84 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+
+
+class AddServerTest(BaseTestGenerator):
+ """ This class will add the servers under default server group. """
+
+ scenarios = utils.generate_scenarios('add_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ pass
+
+ def create_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
+ def runTest(self):
+ """ This function will add the server under default server group."""
+ url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
+
+ # Add service name in the config
+ if 'connect_timeout' in self.test_data:
+ self.server['connect_timeout'] = self.test_data['connect_timeout']
+ elif 'shared' in self.test_data:
+ self.server['shared'] = self.test_data['shared']
+ elif 'service' in self.test_data:
+ self.server['service'] = self.test_data['service']
+
+ if hasattr(self, 'ssh_tunnel'):
+ self.server['use_ssh_tunnel'] = self.test_data['use_ssh_tunnel']
+ self.server['tunnel_host'] = self.test_data['tunnel_host']
+ self.server['tunnel_port'] = self.test_data['tunnel_port']
+ self.server['tunnel_username'] = self.test_data['tunnel_username']
+
+ if self.with_password:
+ self.server['tunnel_authentication'] = self.test_data[
+ 'tunnel_authentication']
+ else:
+ self.server['tunnel_authentication'] = 1
+ self.server['tunnel_identity_file'] = 'pkey_rsa'
+
+ if self.save_password:
+ self.server['tunnel_password'] = self.test_data[
+ 'tunnel_password']
+ if 'connect_now' in self.test_data:
+ self.server['connect_now'] = self.test_data['connect_now']
+ self.server['password'] = self.server['db_password']
+
+ if self.is_positive_test:
+ if hasattr(self, 'with_save'):
+ self.server['save_password'] = self.with_save
+ if hasattr(self, 'with_pwd') and not self.with_pwd:
+ # Remove the password from server object
+ db_password = self.server['db_password']
+ del self.server['db_password']
+ response = self.create_server(url)
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.server_id = response_data['node']['_id']
+
+ if hasattr(self, 'with_pwd') and not self.with_pwd:
+ # Remove the password from server object
+ self.server['db_password'] = db_password
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py
deleted file mode 100644
index cce03041a..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py
+++ /dev/null
@@ -1,47 +0,0 @@
-##########################################################################
-#
-# pgAdmin 4 - PostgreSQL Tools
-#
-# Copyright (C) 2013 - 2020, The pgAdmin Development Team
-# This software is released under the PostgreSQL Licence
-#
-##########################################################################
-
-import json
-
-from pgadmin.utils.route import BaseTestGenerator
-from regression.python_test_utils import test_utils as utils
-
-
-class ServersWithConnectTimeoutAddTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- # Fetch the default url for server object
- (
- 'Default Server Node url', dict(
- url='/browser/server/obj/'
- )
- )
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- # Add service name in the config
- self.server['connect_timeout'] = 5
- response = self.tester.post(
- url,
- data=json.dumps(self.server),
- content_type='html/json'
- )
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py
deleted file mode 100644
index 9d8da94fd..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py
+++ /dev/null
@@ -1,47 +0,0 @@
-##########################################################################
-#
-# pgAdmin 4 - PostgreSQL Tools
-#
-# Copyright (C) 2013 - 2020, The pgAdmin Development Team
-# This software is released under the PostgreSQL Licence
-#
-##########################################################################
-
-import json
-
-from pgadmin.utils.route import BaseTestGenerator
-from regression.python_test_utils import test_utils as utils
-
-
-class ServersWithServiceIDAddTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- # Fetch the default url for server object
- (
- 'Default Server Node url', dict(
- url='/browser/server/obj/'
- )
- )
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- # Add service name in the config
- self.server['service'] = "TestDB"
- response = self.tester.post(
- url,
- data=json.dumps(self.server),
- content_type='html/json'
- )
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py
deleted file mode 100644
index e8b48c0fc..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py
+++ /dev/null
@@ -1,82 +0,0 @@
-##########################################################################
-#
-# pgAdmin 4 - PostgreSQL Tools
-#
-# Copyright (C) 2013 - 2020, The pgAdmin Development Team
-# This software is released under the PostgreSQL Licence
-#
-##########################################################################
-
-import json
-
-from pgadmin.utils.route import BaseTestGenerator
-from regression.python_test_utils import test_utils as utils
-
-
-class ServersWithSSHTunnelAddTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- (
- 'Add server using SSH tunnel with password', dict(
- url='/browser/server/obj/',
- with_password=True,
- save_password=False,
- )
- ),
- (
- 'Add server using SSH tunnel with identity file', dict(
- url='/browser/server/obj/',
- with_password=False,
- save_password=False,
- )
- ),
- (
- 'Add server using SSH tunnel with password and saved it', dict(
- url='/browser/server/obj/',
- with_password=True,
- save_password=True,
- )
- ),
- (
- 'Add server using SSH tunnel with identity file and save the '
- 'password', dict(
- url='/browser/server/obj/',
- with_password=False,
- save_password=True,
- )
- ),
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- # Add service name in the config
- self.server['use_ssh_tunnel'] = 1
- self.server['tunnel_host'] = '127.0.0.1'
- self.server['tunnel_port'] = 22
- self.server['tunnel_username'] = 'user'
- if self.with_password:
- self.server['tunnel_authentication'] = 0
- else:
- self.server['tunnel_authentication'] = 1
- self.server['tunnel_identity_file'] = 'pkey_rsa'
-
- if self.save_password:
- self.server['tunnel_password'] = '123456'
-
- response = self.tester.post(
- url,
- data=json.dumps(self.server),
- content_type='html/json'
- )
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_all_server_get.py b/web/pgadmin/browser/server_groups/servers/tests/test_all_server_get.py
new file mode 100644
index 000000000..cd35a7a05
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_all_server_get.py
@@ -0,0 +1,78 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import random
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+
+
+class AllServersGetTestCase(BaseTestGenerator):
+ """
+ This class will fetch added servers under default server group
+ by response code.
+ """
+
+ scenarios = utils.generate_scenarios('get_all_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ """This function add the server to test the GET API"""
+ self.server['password'] = 'edb'
+ self.server_id = utils.create_server(self.server)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def get_server(self):
+ return self.tester.get(self.url, follow_redirects=True)
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=self.server,
+ content_type='html/json'
+ )
+
+ def runTest(self):
+ """ This function will fetch the added servers to object browser. """
+ server_id = parent_node_dict["server"][-1]["server_id"]
+ if not server_id:
+ raise Exception("Server not found to test GET API")
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, 'invalid_server_group'):
+ self.url = self.url + '{0}/{1}?_={1}'.format(
+ utils.SERVER_GROUP, random.randint(1, 9999999))
+ elif hasattr(self, 'children'):
+
+ self.url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP, server_id)
+ elif hasattr(self, 'server_list'):
+ if hasattr(self, 'servers'):
+ server_id = ''
+ self.url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP, server_id)
+ else:
+ if hasattr(self, "connected"):
+ url = '/browser/server/connect/' + '{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ self.server['password'] = 'edb'
+
+ self.connect_to_server(url)
+ self.url = self.url + '{0}/{1}?_={2}'.format(
+ utils.SERVER_GROUP, server_id, random.randint(1, 9999999))
+ response = self.get_server()
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py b/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
new file mode 100644
index 000000000..cca7ce57f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
@@ -0,0 +1,122 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+import json
+from regression.test_setup import config_data
+
+test_user_details = config_data['pgAdmin4_test_non_admin_credentials']
+
+
+class ServersConnectTestCase(BaseTestGenerator):
+ """
+ This class will fetch added servers under default server group
+ by response code.
+ """
+
+ scenarios = utils.generate_scenarios('connect_server',
+ servers_utils.test_cases)
+
+ def get_ssh_tunnel(self):
+ print("in_get_ssh")
+ self.server['use_ssh_tunnel'] = 1
+ self.server['tunnel_host'] = '127.0.0.1'
+ self.server['tunnel_port'] = 22
+ self.server['tunnel_username'] = 'user'
+ if self.with_password:
+ self.server['tunnel_authentication'] = 0
+ else:
+ self.server['tunnel_authentication'] = 1
+ self.server['tunnel_identity_file'] = 'pkey_rsa'
+
+ if self.save_password:
+ self.server['tunnel_password'] = '123456'
+ # self.server['use_ssh_tunnel'] = self.test_data['use_ssh_tunnel']
+ # self.server['tunnel_host'] = self.test_data['tunnel_host']
+ # self.server['tunnel_port'] = self.test_data['tunnel_port']
+ # self.server['tunnel_username'] = self.test_data['tunnel_username']
+ #
+ # if self.with_password:
+ # self.server['tunnel_authentication'] = self.test_data[
+ # 'tunnel_authentication']
+ #
+ # if self.save_password:
+ # self.server['tunnel_password'] = self.test_data[
+ # 'tunnel_password']
+
+ def setUp(self):
+ """This function add the server to test the GET API"""
+
+ self.server_id = utils.create_server(self.server)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def get_server_connection(self, server_id):
+ return self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id),
+ follow_redirects=True)
+
+ def server_disonnect(self, server_id):
+ return self.tester.delete(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id))
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
+ def add_server_details(self, url):
+ return self.tester.post(
+ url,
+ data=str(self.test_data),
+ content_type='html/json'
+ )
+
+ def runTest(self):
+ """ This function will fetch the added servers to object browser. """
+ server_id = parent_node_dict["server"][-1]["server_id"]
+ if not server_id:
+ raise Exception("Server not found to test GET API")
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, 'disconnect'):
+ if hasattr(self, 'wrong_server_id'):
+ server_id = 99999
+ response = self.server_disonnect(server_id)
+ elif hasattr(self, "connect"):
+ url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ self.server['password'] = self.server['db_password']
+ response = self.connect_to_server(url)
+ elif hasattr(self, 'restore_point') or hasattr(self,
+ 'change_password'):
+ connect_url = '/browser/server/connect/{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+
+ self.connect_to_server(connect_url)
+ response = self.add_server_details(url)
+ else:
+ response = self.get_server_connection(server_id)
+
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_is_password_saved.py b/web/pgadmin/browser/server_groups/servers/tests/test_is_password_saved.py
new file mode 100644
index 000000000..7d144d3d7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_is_password_saved.py
@@ -0,0 +1,52 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+
+
+class IsPasswordSaved(BaseTestGenerator):
+ """ This class will test the save password functionality. """
+
+ scenarios = utils.generate_scenarios('is_password_saved',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ self.server_id = utils.create_server(self.server)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def runTest(self):
+ """This function will execute the connect server APIs"""
+ response = self.tester.post(
+ self.url + str(utils.SERVER_GROUP) + '/' + str(self.server_id),
+ data=dict(
+ password=self.server['db_password'],
+ save_password='on'),
+ follow_redirects=True)
+
+ expected_status_code = self.expected_data["status_code"]
+ actual_status_code = response.status_code
+ self.assertEquals(actual_status_code, expected_status_code)
+ response_data = json.loads(response.data.decode('utf-8'))
+
+ expected_message = self.expected_data["message"]
+ actual_message = response_data["info"]
+ self.assertEquals(actual_message, expected_message)
+
+ expected_is_password_saved = self.test_data["is_password_saved"]
+ actual_is_password_saved = response_data["data"]["is_password_saved"]
+ self.assertEquals(actual_is_password_saved, expected_is_password_saved)
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_add.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_add.py
deleted file mode 100644
index 7fe3cf6a8..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_add.py
+++ /dev/null
@@ -1,86 +0,0 @@
-##########################################################################
-#
-# pgAdmin 4 - PostgreSQL Tools
-#
-# Copyright (C) 2013 - 2020, The pgAdmin Development Team
-# This software is released under the PostgreSQL Licence
-#
-##########################################################################
-
-import json
-import copy
-from pgadmin.utils.route import BaseTestGenerator
-from regression.python_test_utils import test_utils as utils
-
-
-class ServersAddTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- # Fetch the default url for server object
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- response = self.tester.post(url, data=json.dumps(self.server),
- content_type='html/json')
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
- server_dict = {"server_id": int(self.server_id)}
- utils.write_node_info("sid", server_dict)
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
-
-
-class AddServersWithSavePasswordTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- # Fetch the default url for server object
- ('Add server with password and save password to true',
- dict(url='/browser/server/obj/', with_pwd=True, with_save=True)),
- ('Add server with password and save password to false',
- dict(url='/browser/server/obj/', with_pwd=True, with_save=False)),
- ('Add server without password and save password to true',
- dict(url='/browser/server/obj/', with_pwd=False, with_save=True)),
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- _server = copy.deepcopy(self.server)
- # Update the flag as required
- _server['save_password'] = self.with_save
- if not self.with_pwd:
- # Remove the password from server object
- del _server['db_password']
-
- response = self.tester.post(url, data=json.dumps(_server),
- content_type='html/json')
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
- server_dict = {"server_id": int(self.server_id)}
- # Fetch the node info to check if password was saved or not
- response = self.tester.get(self.url.replace('obj', 'nodes') +
- str(utils.SERVER_GROUP) + '/' +
- str(self.server_id),
- follow_redirects=True)
- self.assertEquals(response.status_code, 200)
- self.assertTrue('is_password_saved' in response.json['result'])
- utils.write_node_info("sid", server_dict)
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py
index c2b184295..43220eb2f 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py
@@ -9,15 +9,14 @@
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
class ServerDeleteTestCase(BaseTestGenerator):
""" This class will delete the last server present under tree node."""
- scenarios = [
- # Fetching the default url for server node
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
+ scenarios = utils.generate_scenarios('delete_server',
+ servers_utils.test_cases)
def setUp(self):
"""This function add the server to test the DELETE API"""
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py
index 9f92ffcce..5a83e2a08 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py
@@ -10,6 +10,8 @@
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+import json
class ServersGetTestCase(BaseTestGenerator):
@@ -18,26 +20,50 @@ class ServersGetTestCase(BaseTestGenerator):
by response code.
"""
- scenarios = [
- # Fetch the default url for server node
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
+ scenarios = utils.generate_scenarios('get_server',
+ servers_utils.test_cases)
def setUp(self):
"""This function add the server to test the GET API"""
- self.server_id = utils.create_server(self.server)
+ if hasattr(self, 'shared'):
+
+ self.server['shared'] = True
+ url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
+ response = self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.server_id = response_data['node']['_id']
+ else:
+ self.server_id = utils.create_server(self.server)
server_dict = {"server_id": self.server_id}
utils.write_node_info("sid", server_dict)
+ def get_server(self, server_id):
+ return self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id),
+ follow_redirects=True)
+
def runTest(self):
""" This function will fetch the added servers to object browser. """
server_id = parent_node_dict["server"][-1]["server_id"]
if not server_id:
raise Exception("Server not found to test GET API")
- response = self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
- str(server_id),
- follow_redirects=True)
- self.assertEquals(response.status_code, 200)
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, "incorrect_server_id"):
+ server_id = 9999
+ if hasattr(self, "server_list"):
+ server_id = ''
+ if hasattr(self, "server_node"):
+ server_id = ''
+ if hasattr(self, 'shared'):
+ server_id = self.server_id
+ response = self.get_server(server_id)
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
def tearDown(self):
"""This function delete the server from SQLite """
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py
index 8a69558e9..74674c7cb 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py
@@ -11,32 +11,61 @@ import json
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
class ServerUpdateTestCase(BaseTestGenerator):
""" This class will update server's comment field. """
- scenarios = [
- # Fetching the default url for server node
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
+ scenarios = utils.generate_scenarios('update_server',
+ servers_utils.test_cases)
def setUp(self):
"""This function add the server to test the PUT API"""
- self.server_id = utils.create_server(self.server)
+ if hasattr(self, 'clear_save_password'):
+ self.server['save_password'] = 1
+ create_server_url = "/browser/server/obj/{0}/".format(
+ utils.SERVER_GROUP)
+
+ self.server_id = \
+ servers_utils.create_server_with_api(self, create_server_url)
server_dict = {"server_id": self.server_id}
utils.write_node_info("sid", server_dict)
+ def update_server(self):
+ return self.tester.put(
+ self.url + str(utils.SERVER_GROUP) + '/' +
+ str(self.server_id), data=json.dumps(self.test_data),
+ content_type='html/json')
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
def runTest(self):
"""This function update the server details"""
if not self.server_id:
raise Exception("No server to update.")
- data = {"comment": self.server['comment'], "id": self.server_id}
- put_response = self.tester.put(
- self.url + str(utils.SERVER_GROUP) + '/' +
- str(self.server_id), data=json.dumps(data),
- content_type='html/json')
- self.assertEquals(put_response.status_code, 200)
+ if 'comment' in self.test_data:
+ self.test_data["comment"] = self.server['comment']
+ self.test_data["id"] = self.server_id
+ if self.is_positive_test:
+ if hasattr(self, 'server_connected'):
+ url = '/browser/server/connect/{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ self.server['password'] = self.server['db_password']
+ self.connect_to_server(url)
+ put_response = self.update_server()
+ else:
+ if hasattr(self, 'wrong_server_id'):
+ self.server_id = 9999
+ put_response = self.update_server()
+ self.assertEquals(put_response.status_code,
+ self.expected_data["status_code"])
def tearDown(self):
"""This function delete the server from SQLite"""
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py b/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
new file mode 100644
index 000000000..bf34ad123
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
@@ -0,0 +1,125 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+import json
+from regression.test_setup import config_data
+from regression.python_test_utils.test_utils import \
+ create_user_wise_test_client
+
+test_user_details = config_data[
+ 'pgAdmin4_test_non_admin_credentials']
+
+
+class SharedServersGetTestCase(BaseTestGenerator):
+ """
+ This class will fetch added servers under default server group
+ by response code.
+ """
+
+ scenarios = utils.generate_scenarios('get_shared_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ """This function add the server to test the GET API"""
+ self.server['shared'] = True
+ url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
+ response = self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.server_id = response_data['node']['_id']
+
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def get_server(self, server_id):
+ return self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id),
+ follow_redirects=True)
+
+ @create_user_wise_test_client(test_user_details)
+ def runTest(self):
+ """ This function will fetch the added servers to object browser. """
+ if not self.server_id:
+ raise Exception("Server not found to test GET API")
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, 'no_server_id'):
+ if hasattr(self, 'server_list'):
+ self.url = '/browser/server/nodes/'
+ server_id = ''
+ response = self.get_server(server_id)
+ else:
+ response = self.get_server(self.server_id)
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
+
+
+class SharedServerUpdateTestCase(BaseTestGenerator):
+ """ This class will update server's comment field. """
+
+ scenarios = utils.generate_scenarios('update_shared_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ """This function add the server to test the PUT API"""
+ self.server['shared'] = True
+ if hasattr(self, 'clear_save_password'):
+ self.server['save_password'] = 1
+ create_server_url = "/browser/server/obj/{0}/".format(
+ utils.SERVER_GROUP)
+
+ self.server_id = \
+ servers_utils.create_server_with_api(self, create_server_url)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def update_server(self):
+ return self.tester.put(
+ self.url + str(utils.SERVER_GROUP) + '/' +
+ str(self.server_id), data=json.dumps(self.test_data),
+ content_type='html/json')
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
+ @create_user_wise_test_client(test_user_details)
+ def runTest(self):
+ """This function update the server details"""
+ if not self.server_id:
+ raise Exception("No server to update.")
+ if 'comment' in self.test_data:
+ self.test_data["comment"] = self.server['comment']
+ self.test_data["id"] = self.server_id
+ if self.is_positive_test:
+ put_response = self.update_server()
+ else:
+ if hasattr(self, 'wrong_server_id'):
+ self.server_id = 9999
+ put_response = self.update_server()
+ self.assertEquals(put_response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite"""
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/utils.py b/web/pgadmin/browser/server_groups/servers/tests/utils.py
new file mode 100644
index 000000000..8680b29fb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/utils.py
@@ -0,0 +1,52 @@
+import os
+import json
+import sqlite3
+import config
+from regression.python_test_utils import test_utils as utils
+
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+with open(CURRENT_PATH + "/servers_test_data.json") as data_file:
+ test_cases = json.load(data_file)
+
+
+def create_server(server, SERVER_GROUP):
+ """This function is used to create server"""
+ try:
+ conn = sqlite3.connect(config.TEST_SQLITE_PATH)
+ # Create the server
+ cur = conn.cursor()
+ if 'shared' not in server:
+ server['shared'] = False
+ server_details = (1, SERVER_GROUP, server['name'], server['host'],
+ server['port'], server['db'], server['username'],
+ server['role'], server['sslmode'], server['comment'],
+ server['shared'])
+ cur.execute('INSERT INTO server (user_id, servergroup_id, name, host, '
+ 'port, maintenance_db, username, role, ssl_mode,'
+ ' comment, shared) VALUES (?,?,?,?,?,?,?,?,?,?,?)',
+ server_details)
+ server_id = cur.lastrowid
+ conn.commit()
+ conn.close()
+
+ type = utils.get_server_type(server)
+ server['type'] = type
+
+ return server_id
+ except Exception as exception:
+ raise Exception("Error while creating server. %s" % exception)
+
+
+def create_server_with_api(self, url):
+ try:
+ response = self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+ response_data = json.loads(response.data.decode('utf-8'))
+ server_id = response_data['node']['_id']
+ return server_id
+ except Exception as exception:
+ raise Exception("Error while creating server. %s" % exception)
diff --git a/web/pgadmin/browser/server_groups/static/img/server_group_shared.svg b/web/pgadmin/browser/server_groups/static/img/server_group_shared.svg
new file mode 100644
index 000000000..6a4e22bf8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/static/img/server_group_shared.svg
@@ -0,0 +1,42 @@
+
+
+
diff --git a/web/pgadmin/browser/server_groups/static/js/server_group.js b/web/pgadmin/browser/server_groups/static/js/server_group.js
index 9bd7df0f3..219b5954a 100644
--- a/web/pgadmin/browser/server_groups/static/js/server_group.js
+++ b/web/pgadmin/browser/server_groups/static/js/server_group.js
@@ -9,8 +9,8 @@
define('pgadmin.node.server_group', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
- 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.node',
-], function(gettext, url_for, $, _, pgAdmin) {
+ 'sources/pgadmin', 'pgadmin.user_management.current_user', 'pgadmin.browser', 'pgadmin.browser.node',
+], function(gettext, url_for, $, _, pgAdmin, current_user) {
if (!pgAdmin.Browser.Nodes['server_group']) {
pgAdmin.Browser.Nodes['server_group'] = pgAdmin.Browser.Node.extend({
@@ -39,14 +39,27 @@ define('pgadmin.node.server_group', [
defaults: {
id: undefined,
name: null,
+ user_id: undefined,
},
schema: [
{
id: 'id', label: gettext('ID'), type: 'int', group: null,
mode: ['properties'],
+ visible: function(model){
+ if (model.attributes.user_id != current_user.id && !current_user.is_admin)
+ return false;
+
+ return true;
+ },
},{
id: 'name', label: gettext('Name'), type: 'text', group: null,
mode: ['properties', 'edit', 'create'],
+ disabled: function(model){
+ if (model.attributes.user_id != current_user.id && !_.isUndefined(model.attributes.user_id))
+ return true;
+
+ return false;
+ },
},
],
validate: function() {
@@ -69,7 +82,11 @@ define('pgadmin.node.server_group', [
return null;
},
}),
- canDrop: function(itemData) { return itemData.can_delete; },
+ canDrop: function(itemData) {
+ var serverOwner = itemData.user_id;
+ if (serverOwner != current_user.id)
+ return false;
+ return itemData.can_delete; },
dropAsRemove: true,
canDelete: function(i) {
var s = pgAdmin.Browser.tree.siblings(i, true);
diff --git a/web/pgadmin/browser/server_groups/templates/css/server_group.css b/web/pgadmin/browser/server_groups/templates/css/server_group.css
new file mode 100644
index 000000000..3b5b8e58a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/templates/css/server_group.css
@@ -0,0 +1,17 @@
+.icon-server_group {
+ background-image: url('{{ url_for('NODE-server_group.static', filename='img/server_group.svg') }}') !important;
+ background-repeat: no-repeat;
+ background-size: 20px !important;
+ align-content: center;
+ vertical-align: middle;
+ height: 1.3em;
+}
+
+.icon-server_group_shared {
+ background-image: url('{{ url_for('NODE-server_group.static', filename='img/server_group_shared.svg') }}') !important;
+ background-repeat: no-repeat;
+ background-size: 20px !important;
+ align-content: center;
+ vertical-align: middle;
+ height: 1.3em;
+}
diff --git a/web/pgadmin/browser/templates/browser/js/utils.js b/web/pgadmin/browser/templates/browser/js/utils.js
index 33dd6809e..516b62697 100644
--- a/web/pgadmin/browser/templates/browser/js/utils.js
+++ b/web/pgadmin/browser/templates/browser/js/utils.js
@@ -44,6 +44,7 @@ define('pgadmin.browser.utils',
pgAdmin['csrf_token_header'] = '{{ current_app.config.get('WTF_CSRF_HEADERS')[0] }}';
pgAdmin['csrf_token'] = '{{ csrf_token() }}';
+ pgAdmin['server_mode'] = '{{ current_app.config.get('SERVER_MODE') }}';
/* Get the inactivity related config */
pgAdmin['user_inactivity_timeout'] = {{ current_app.config.get('USER_INACTIVITY_TIMEOUT') }};
diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py
index 03681e120..b33adc062 100644
--- a/web/pgadmin/model/__init__.py
+++ b/web/pgadmin/model/__init__.py
@@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy
#
##########################################################################
-SCHEMA_VERSION = 25
+SCHEMA_VERSION = 26
##########################################################################
#
@@ -173,6 +173,7 @@ class Server(db.Model):
)
tunnel_identity_file = db.Column(db.String(64), nullable=True)
tunnel_password = db.Column(db.String(64), nullable=True)
+ shared = db.Column(db.Boolean(), nullable=False)
class ModulePreference(db.Model):
@@ -305,3 +306,88 @@ class Database(db.Model):
nullable=False,
primary_key=True
)
+
+
+class SharedServer(db.Model):
+ """Define a shared Postgres server"""
+
+ __tablename__ = 'sharedserver'
+ id = db.Column(db.Integer, primary_key=True)
+ user_id = db.Column(
+ db.Integer,
+ db.ForeignKey('user.id')
+ )
+ server_owner = db.Column(
+ db.String(128),
+ db.ForeignKey('user.username')
+ )
+ servergroup_id = db.Column(
+ db.Integer,
+ db.ForeignKey('servergroup.id'),
+ nullable=False
+ )
+ name = db.Column(db.String(128), nullable=False)
+ host = db.Column(db.String(128), nullable=True)
+ hostaddr = db.Column(db.String(128), nullable=True)
+ port = db.Column(
+ db.Integer(),
+ db.CheckConstraint('port >= 1 AND port <= 65534'),
+ nullable=False)
+ maintenance_db = db.Column(db.String(64), nullable=True)
+ username = db.Column(db.String(64), nullable=False)
+ password = db.Column(db.String(64), nullable=True)
+ save_password = db.Column(
+ db.Integer(),
+ db.CheckConstraint('save_password >= 0 AND save_password <= 1'),
+ nullable=False
+ )
+ role = db.Column(db.String(64), nullable=True)
+ ssl_mode = db.Column(
+ db.String(16),
+ db.CheckConstraint(
+ "ssl_mode IN ('allow', 'prefer', 'require', 'disable', "
+ "'verify-ca', 'verify-full')"
+ ),
+ nullable=False)
+ comment = db.Column(db.String(1024), nullable=True)
+ discovery_id = db.Column(db.String(128), nullable=True)
+ servers = db.relationship(
+ 'ServerGroup',
+ backref=db.backref('sharedserver', cascade="all, delete-orphan"),
+ lazy='joined'
+ )
+ db_res = db.Column(db.Text(), nullable=True)
+ passfile = db.Column(db.Text(), nullable=True)
+ sslcert = db.Column(db.Text(), nullable=True)
+ sslkey = db.Column(db.Text(), nullable=True)
+ sslrootcert = db.Column(db.Text(), nullable=True)
+ sslcrl = db.Column(db.Text(), nullable=True)
+ sslcompression = db.Column(
+ db.Integer(),
+ db.CheckConstraint('sslcompression >= 0 AND sslcompression <= 1'),
+ nullable=False
+ )
+ bgcolor = db.Column(db.Text(10), nullable=True)
+ fgcolor = db.Column(db.Text(10), nullable=True)
+ service = db.Column(db.Text(), nullable=True)
+ connect_timeout = db.Column(db.Integer(), nullable=False)
+ use_ssh_tunnel = db.Column(
+ db.Integer(),
+ db.CheckConstraint('use_ssh_tunnel >= 0 AND use_ssh_tunnel <= 1'),
+ nullable=False
+ )
+ tunnel_host = db.Column(db.String(128), nullable=True)
+ tunnel_port = db.Column(
+ db.Integer(),
+ db.CheckConstraint('port <= 65534'),
+ nullable=True)
+ tunnel_username = db.Column(db.String(64), nullable=True)
+ tunnel_authentication = db.Column(
+ db.Integer(),
+ db.CheckConstraint('tunnel_authentication >= 0 AND '
+ 'tunnel_authentication <= 1'),
+ nullable=False
+ )
+ tunnel_identity_file = db.Column(db.String(64), nullable=True)
+ tunnel_password = db.Column(db.String(64), nullable=True)
+ shared = db.Column(db.Boolean(), nullable=False)
diff --git a/web/pgadmin/utils/driver/psycopg2/server_manager.py b/web/pgadmin/utils/driver/psycopg2/server_manager.py
index ea1d9efe1..77c4dec22 100644
--- a/web/pgadmin/utils/driver/psycopg2/server_manager.py
+++ b/web/pgadmin/utils/driver/psycopg2/server_manager.py
@@ -66,6 +66,7 @@ class ServerManager(object):
self.hostaddr = server.hostaddr
self.port = server.port
self.db = server.maintenance_db
+ self.shared = server.shared
self.did = None
self.user = server.username
self.password = server.password
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 2f36b5e81..ae60e379f 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -36,6 +36,8 @@ from regression import test_setup
from pgadmin.utils.preferences import Preferences
+from functools import wraps
+
CURRENT_PATH = os.path.abspath(os.path.join(os.path.dirname(
os.path.realpath(__file__)), "../"))
@@ -1585,3 +1587,110 @@ def get_selenoid_browsers_list(arguments):
list_of_browsers = test_setup.config_data['selenoid_config'][
'browsers_list']
return list_of_browsers
+
+
+def login_using_user_account(tester):
+ """
+ This function login the test client username and password
+ :param tester: test client
+ :type tester: flask test client object
+ :return: None
+ """
+ username = tester.test_config_data['login_username']
+ password = tester.test_config_data['login_password']
+ response = tester.login(username, password)
+
+ if response.status_code != 302:
+ print("Unable to login test client, email and password not found.",
+ file=sys.stderr)
+ sys.exit(1)
+
+
+def logout_tester_account(tester):
+ """
+ This function logout the test account
+ :param tester: test client
+ :type tester: flask test client object
+ :return: None
+ """
+ tester.logout()
+
+
+def create_user(user_details):
+ try:
+ conn = sqlite3.connect(config.TEST_SQLITE_PATH)
+ # Create the server
+ cur = conn.cursor()
+ user_details = (
+ user_details['login_username'], user_details['login_username'],
+ user_details['login_password'], 1)
+
+ cur.execute(
+ 'select * from user where username = "%s"' % user_details[0])
+ user = cur.fetchone()
+ if user is None:
+ cur.execute('INSERT INTO user (username, email, password, active) '
+ 'VALUES (?,?,?,?)', user_details)
+ user_id = cur.lastrowid
+ conn.commit()
+ else:
+ user_id = user[0]
+ conn.close()
+
+ return user_id
+ except Exception as exception:
+ raise Exception("Error while creating server. %s" % exception)
+
+
+def get_test_user(self, user_details,
+ is_api=True, create_conn=True):
+ # assert id == 1 or id == 0
+ test_client = self.app.test_client()
+ if user_details is None:
+ return None, None
+
+ if is_api is True:
+
+ # Create test_client for this user, and login through it.
+ test_client = self.app.test_client()
+ user = create_user(user_details)
+ if user is not None:
+ test_client.test_config_data = dict({
+ "login_username": user_details['login_username'],
+ "login_password": user_details['login_password']
+ })
+ else:
+ return "User not created"
+ login_using_user_account(test_client)
+ user = test_client
+
+ return user
+
+
+def create_user_wise_test_client(user):
+ """
+ This function creates new test client and pem database connection as per
+ provided user and execute the test cases.
+ :return: None
+ """
+
+ def multi_user_decorator(func):
+ @wraps(func)
+ def wrapper(self, *args, **kwargs):
+ # self.user = user
+ main_tester = self.__class__.tester
+ try:
+ # Login with non-admin_user
+ test_user = get_test_user(self, user)
+ self.setTestClient(test_user)
+ # self.setTestClient(test_user['tester'])
+
+ # Call 'runTest' with new test client
+ func(self, *args, **kwargs)
+ finally:
+ # Restore the original user and driver
+ self.__class__.tester = main_tester
+
+ return wrapper
+
+ return multi_user_decorator
diff --git a/web/setup.py b/web/setup.py
index 14bd5f9cc..e8a71f812 100644
--- a/web/setup.py
+++ b/web/setup.py
@@ -110,6 +110,7 @@ def dump_servers(args):
add_value(attr_dict, "Role", server.role)
add_value(attr_dict, "SSLMode", server.ssl_mode)
add_value(attr_dict, "Comment", server.comment)
+ add_value(attr_dict, "Shared", server.shared)
add_value(attr_dict, "DBRestriction", server.db_res)
add_value(attr_dict, "PassFile", server.passfile)
add_value(attr_dict, "SSLCert", server.sslcert)
@@ -244,6 +245,14 @@ def load_servers(args):
print_summary()
sys.exit(1)
+ # Check if server is shared.Won't import if user is non-admin
+ if 'Shared' in obj:
+ if obj['Shared'] and \
+ not user.has_role("Administrator"):
+ print("Can't import the server '%s' as it is shared " %
+ obj["Name"])
+ continue
+
# Get the group. Create if necessary
group_id = -1
for g in groups: