public inbox for [email protected]  
help / color / mirror / Atom feed
From: Pradip Parkale <[email protected]>
To: Akshay Joshi <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
Date: Fri, 21 Aug 2020 18:00:33 +0530
Message-ID: <CAJ9T6SsV50gwjNP3D4YhONhtV6=RfgqE4hUGoDf91aBZMcahsA@mail.gmail.com> (raw)
In-Reply-To: <CANxoLDcmxQsdmO-8Z9O6=zmuUs5KGkXzCmjqyoatCRCd=xBkdA@mail.gmail.com>
References: <CAJ9T6SvOzSPGyRgVg+8XksV_4=mOSSMoy8RaGKm0O9qFTT_ZQQ@mail.gmail.com>
	<CANxoLDcmxQsdmO-8Z9O6=zmuUs5KGkXzCmjqyoatCRCd=xBkdA@mail.gmail.com>

Hi Akshay,
Please find the updated patch.

On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <[email protected]>
wrote:

> Hi Pradip
>
> The patch is not applied, can you please rebase and send it again.
>
> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
> [email protected]> wrote:
>
>> Hi Hackers,
>>
>> Please find the attached patch for the shared server implementation.
>>
>> Few key points:
>>
>>    1. The admin who is the owner of the server user can share the server
>>    with other users.
>>    2. This option will be available only for admin users.
>>    3. If the user doesn't want to see the shared server then the option
>>    to hide the shared server is available in preferences.
>>    4. The user who is not the owner of the server, can't delete the
>>    shared server and server group.
>>    5. This option is only available in server mode.
>>
>>
>> --
>> Thanks & Regards,
>> Pradip Parkale
>> Software Engineer | EnterpriseDB Corporation
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
> *pgAdmin Hacker | Sr. Software Architect*
> *EDB Postgres <http://edbpostgres.com>*
>
> *Mobile: +91 976-788-8246*
>


-- 
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation


Attachments:

  [application/octet-stream] RM4979_v2.patch (125.9K, 3-RM4979_v2.patch)
  download | inline diff:
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.2.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#F2F2F2;}
+	.st1{fill:#9BA5B0;}
+	.st2{fill:none;stroke:#9BA5B0;stroke-width:0.75;stroke-miterlimit:1;}
+	.st3{fill:#F7F7F7;}
+	.st4{fill:#354A5F;}
+	.st5{fill:none;stroke:#D0021B;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<g>
+	<path class="st0" d="M6.7,2.1h4.6c0.6,0,1.1,0.5,1.1,1.1v0.8c0,0.6-0.5,1.1-1.1,1.1H6.7c-0.6,0-1.1-0.5-1.1-1.1V3.2
+		C5.6,2.6,6.1,2.1,6.7,2.1z"/>
+	<path class="st1" d="M11.3,2.5c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,4.8,6,4.5,6,4.1V3.2
+		c0-0.4,0.3-0.7,0.8-0.7H11.3 M11.3,1.7H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V3.2
+		C12.8,2.4,12.1,1.7,11.3,1.7L11.3,1.7z"/>
+	<line class="st2" x1="8.9" y1="3.6" x2="6.8" y2="3.6"/>
+	<path class="st0" d="M6.7,5.2h4.6c0.6,0,1.1,0.5,1.1,1.1v0.8c0,0.6-0.5,1.1-1.1,1.1H6.7c-0.6,0-1.1-0.5-1.1-1.1V6.3
+		C5.6,5.7,6.1,5.2,6.7,5.2z"/>
+	<path class="st1" d="M11.3,5.6c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,7.9,6,7.6,6,7.1V6.3
+		c0-0.4,0.3-0.7,0.8-0.7H11.3 M11.3,4.8H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V6.3
+		C12.8,5.5,12.1,4.8,11.3,4.8L11.3,4.8z"/>
+	<line class="st2" x1="8.9" y1="6.7" x2="6.8" y2="6.7"/>
+	<path class="st0" d="M6.7,8.3h4.6c0.6,0,1.1,0.5,1.1,1.1v0.9c0,0.6-0.5,1.1-1.1,1.1c0,0,0,0,0,0H6.7c-0.6,0-1.1-0.5-1.1-1.1l0,0
+		V9.4C5.6,8.8,6.1,8.3,6.7,8.3C6.7,8.3,6.7,8.3,6.7,8.3z"/>
+	<path class="st1" d="M11.3,8.7c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,11,6,10.7,6,10.3V9.4C6,9,6.3,8.7,6.7,8.7
+		H11.3 M11.3,7.9H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V9.4C12.8,8.6,12.1,7.9,11.3,7.9z
+		"/>
+	<line class="st2" x1="8.9" y1="9.8" x2="6.8" y2="9.8"/>
+</g>
+<path class="st3" d="M11.6,10.5c-0.6,0.3-0.8,0.1-1.1-0.2c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5C6.8,8.4,6.4,8.3,6.1,8.3
+	C5.1,8.1,4.2,8.4,3.4,9l-1.4,1.1v2.7h0.9v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2l6.1-2.6
+	c0.3-0.1,0.6-0.4,0.7-0.7c0.1-0.3,0.1-0.7,0-1c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2L11.6,10.5"/>
+<path class="st4" d="M15.5,10.2c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2l-1.8,0.8l0.3,0.8l1.8-0.8l0.4-0.2c0.2-0.1,0.5,0,0.6,0.2
+	c0.1,0.1,0.1,0.2,0,0.3c0,0.1-0.1,0.2-0.2,0.2l-6.1,2.6c-0.5,0.2-1,0.2-1.4,0l-4.1-2v-1.2l1-0.8C4.5,9.2,5.2,9,6,9.1
+	c0.3,0,0.6,0.1,0.8,0.2l2.8,1.2c0.1,0,0.1,0.1,0.2,0.2c0.1,0.1,0.1,0.3,0,0.4c0,0,0,0.1-0.1,0.1c-0.1,0.1-0.3,0.2-0.5,0.1l-2.2-1
+	l-0.4,0.8l2.2,1c0.2,0.1,0.4,0.1,0.5,0.1c0.4,0,0.7-0.2,1-0.4c0.1-0.1,0.2-0.2,0.2-0.3c0.2-0.4,0.1-0.9-0.1-1.2
+	c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5C6.8,8.4,6.4,8.3,6.1,8.3C5.1,8.1,4.2,8.4,3.4,9L2.9,9.4V9.3c0-0.6-0.4-1-1-1H1.4
+	c-0.6,0-1,0.4-1,1v4.1c0,0.6,0.4,1,1,1h0.5c0.6,0,1-0.4,1-1v-0.6h0v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2
+	l6.1-2.6c0.3-0.1,0.6-0.4,0.7-0.7C15.7,10.9,15.7,10.5,15.5,10.2z M1.9,13.4H1.4V9.3h0.5L1.9,13.4z"/>
+<line class="st5" x1="13.8" y1="1.3" x2="10.8" y2="4.3"/>
+<line class="st5" x1="13.8" y1="4.3" x2="10.8" y2="1.3"/>
+</svg>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#F2F2F2;}
+	.st1{fill:#9BA5B0;}
+	.st2{fill:none;stroke:#9BA5B0;stroke-width:0.75;stroke-miterlimit:1;}
+	.st3{fill:#F7F7F7;}
+	.st4{fill:#354A5F;}
+</style>
+<g>
+	<path class="st0" d="M6.7,2.1h4.6c0.6,0,1.1,0.5,1.1,1.1V4c0,0.6-0.5,1.1-1.1,1.1H6.7C6.1,5.1,5.6,4.6,5.6,4V3.2
+		C5.6,2.6,6.1,2.1,6.7,2.1z"/>
+	<path class="st1" d="M11.3,2.5c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,4.8,6,4.5,6,4.1V3.2
+		c0-0.4,0.3-0.7,0.8-0.7H11.3 M11.3,1.7H6.7c-0.8,0-1.5,0.7-1.5,1.5V4c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V3.2
+		C12.8,2.4,12.1,1.7,11.3,1.7L11.3,1.7z"/>
+	<line class="st2" x1="8.9" y1="3.6" x2="6.8" y2="3.6"/>
+	<path class="st0" d="M6.7,5.2h4.6c0.6,0,1.1,0.5,1.1,1.1v0.8c0,0.6-0.5,1.1-1.1,1.1H6.7c-0.6,0-1.1-0.5-1.1-1.1V6.3
+		C5.6,5.7,6.1,5.2,6.7,5.2z"/>
+	<path class="st1" d="M11.3,5.6c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,7.9,6,7.6,6,7.1V6.3
+		c0-0.4,0.3-0.7,0.8-0.7L11.3,5.6 M11.3,4.8H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V6.3
+		C12.8,5.5,12.1,4.8,11.3,4.8L11.3,4.8z"/>
+	<line class="st2" x1="8.9" y1="6.7" x2="6.8" y2="6.7"/>
+	<path class="st0" d="M6.7,8.3h4.6c0.6,0,1.1,0.5,1.1,1.1v0.9c0,0.6-0.5,1.1-1.1,1.1l0,0H6.7c-0.6,0-1.1-0.5-1.1-1.1l0,0V9.4
+		C5.6,8.8,6.1,8.3,6.7,8.3L6.7,8.3z"/>
+	<path class="st1" d="M11.3,8.7c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,11,6,10.7,6,10.3V9.4C6,9,6.3,8.7,6.7,8.7
+		H11.3 M11.3,7.9H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V9.4C12.8,8.6,12.1,7.9,11.3,7.9z
+		"/>
+	<line class="st2" x1="8.9" y1="9.8" x2="6.8" y2="9.8"/>
+</g>
+<path class="st3" d="M11.6,10.5c-0.6,0.3-0.8,0.1-1.1-0.2c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5c-0.3-0.1-0.7-0.2-1-0.2
+	C5.1,8.1,4.2,8.4,3.4,9L2,10.1v2.7h0.9v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2l6.1-2.6c0.3-0.1,0.6-0.4,0.7-0.7
+	c0.1-0.3,0.1-0.7,0-1c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2L11.6,10.5"/>
+<path class="st4" d="M15.5,10.2c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2l-1.8,0.8l0.3,0.8l1.8-0.8l0.4-0.2c0.2-0.1,0.5,0,0.6,0.2
+	c0.1,0.1,0.1,0.2,0,0.3c0,0.1-0.1,0.2-0.2,0.2l-6.1,2.6c-0.5,0.2-1,0.2-1.4,0l-4.1-2v-1.2l1-0.8C4.5,9.2,5.2,9,6,9.1
+	c0.3,0,0.6,0.1,0.8,0.2l2.8,1.2c0.1,0,0.1,0.1,0.2,0.2c0.1,0.1,0.1,0.3,0,0.4c0,0,0,0.1-0.1,0.1c-0.1,0.1-0.3,0.2-0.5,0.1l-2.2-1
+	l-0.4,0.8l2.2,1c0.2,0.1,0.4,0.1,0.5,0.1c0.4,0,0.7-0.2,1-0.4c0.1-0.1,0.2-0.2,0.2-0.3c0.2-0.4,0.1-0.9-0.1-1.2
+	c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5c-0.3-0.1-0.7-0.2-1-0.2C5.1,8.1,4.2,8.4,3.4,9L2.9,9.4V9.3c0-0.6-0.4-1-1-1H1.4c-0.6,0-1,0.4-1,1
+	v4.1c0,0.6,0.4,1,1,1h0.5c0.6,0,1-0.4,1-1v-0.6l0,0v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2l6.1-2.6
+	c0.3-0.1,0.6-0.4,0.7-0.7C15.7,10.9,15.7,10.5,15.5,10.2z M1.9,13.4H1.4V9.3h0.5V13.4z"/>
+</svg>
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:


view thread (13+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected]
  Subject: Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
  In-Reply-To: <CAJ9T6SsV50gwjNP3D4YhONhtV6=RfgqE4hUGoDf91aBZMcahsA@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox