Hello in this patch I have implemented oauth2

diff --git a/DEPENDENCIES b/DEPENDENCIES
index 6b4d9cfcf..8efe007a1 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -51,6 +51,7 @@ sshtunnel                                                        0.4.0
 ldap3                                                            2.9              LGPL v3                              https://github.com/cannatag/ldap3
 Flask-BabelEx                                                    0.9.4            BSD                                  http://github.com/mrjoes/flask-babelex
 gssapi                                                           1.6.12           LICENSE.txt                          https://github.com/pythongssapi/python-gssapi
+authlib                                                          0.15.3           BSD                                  https://github.com/lepture/authlib
 
 28 dependencies listed.
 
diff --git a/docs/en_US/release_notes.rst b/docs/en_US/release_notes.rst
index 015c3850b..25570b536 100644
--- a/docs/en_US/release_notes.rst
+++ b/docs/en_US/release_notes.rst
@@ -11,6 +11,7 @@ notes for it.
 .. toctree::
    :maxdepth: 1
 
+   release_notes_5_2
    release_notes_5_1
    release_notes_5_0
    release_notes_4_30
diff --git a/docs/en_US/release_notes_5_2.rst b/docs/en_US/release_notes_5_2.rst
new file mode 100644
index 000000000..d9162831a
--- /dev/null
+++ b/docs/en_US/release_notes_5_2.rst
@@ -0,0 +1,22 @@
+************
+Version 5.1
+************
+
+Release date: 2021-04-22
+
+This release contains a number of bug fixes and new features since the release of pgAdmin4 5.1.
+
+New features
+************
+
+
+Housekeeping
+************
+
+| `Issue #5319 <https://redmine.postgresql.org/issues/5319>`_ -  Improve code coverage and API test cases for Server module.
+
+Bug fixes
+*********
+
+| `Issue #6293 <https://redmine.postgresql.org/issues/6293>`_ -  Fixed an issue where the procedure creation is failed when providing the Volatility option.
+| `Issue #6356 <https://redmine.postgresql.org/issues/6356>`_ -  Mark the Apache HTTPD config file as such in the web DEB and RPM packages.
diff --git a/pkg/debian/build.sh b/pkg/debian/build.sh
index e07d9dd1f..505296c49 100755
--- a/pkg/debian/build.sh
+++ b/pkg/debian/build.sh
@@ -72,6 +72,10 @@ fakeroot dpkg-deb --build "${DESKTOPROOT}" "${DISTROOT}/${APP_NAME}-desktop_${AP
 echo "Creating the web package..."
 mkdir "${WEBROOT}/DEBIAN"
 
+cat << EOF > "${WEBROOT}/DEBIAN/conffiles"
+/etc/apache2/conf-available/pgadmin4.conf
+EOF
+
 cat << EOF > "${WEBROOT}/DEBIAN/control"
 Package: ${APP_NAME}-web
 Version: ${APP_LONG_VERSION}
diff --git a/pkg/redhat/build.sh b/pkg/redhat/build.sh
index 09a058cdf..c21fc9006 100755
--- a/pkg/redhat/build.sh
+++ b/pkg/redhat/build.sh
@@ -159,7 +159,7 @@ cp -rfa %{pga_build_root}/web/* \${RPM_BUILD_ROOT}
 
 %files
 /usr/pgadmin4/bin/*
-/etc/httpd/conf.d/*
+%config(noreplace) /etc/httpd/conf.d/*
 EOF
 
 mkdir -p "${WEBROOT}/etc/httpd/conf.d"
diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py
index a73335371..e69619201 100644
--- a/web/pgadmin/__init__.py
+++ b/web/pgadmin/__init__.py
@@ -44,11 +44,13 @@ from pgadmin.utils.csrf import pgCSRFProtect
 from pgadmin import authenticate
 from pgadmin.utils.security_headers import SecurityHeaders
 from pgadmin.utils.constants import KERBEROS
+from pgadmin.utils.constants import OAUTH
 
 # Explicitly set the mime-types so that a corrupted windows registry will not
 # affect pgAdmin 4 to be load properly. This will avoid the issues that may
 # occur due to security fix of X_CONTENT_TYPE_OPTIONS = "nosniff".
 import mimetypes
+
 mimetypes.add_type('application/javascript', '.js')
 mimetypes.add_type('text/css', '.css')
 
@@ -697,19 +699,20 @@ def create_app(app_name=None):
                 )
                 abort(401)
             login_user(user)
-        elif config.SERVER_MODE and\
-                app.PGADMIN_EXTERNAL_AUTH_SOURCE ==\
-                KERBEROS and \
+        elif config.SERVER_MODE and \
                 not current_user.is_authenticated and \
                 request.endpoint in ('redirects.index', 'security.login'):
-            return authenticate.login()
-
+            if app.PGADMIN_EXTERNAL_AUTH_SOURCE == KERBEROS:
+                return authenticate.login()
+            elif app.PGADMIN_EXTERNAL_AUTH_SOURCE == OAUTH and len(config.AUTHENTICATION_SOURCES) == 1:
+                # forwarding to OAuth if only one authentication is enabled
+                return authenticate.oauth_login()
         # if the server is restarted the in memory key will be lost
         # but the user session may still be active. Logout the user
         # to get the key again when login
         if config.SERVER_MODE and current_user.is_authenticated and \
                 app.PGADMIN_EXTERNAL_AUTH_SOURCE != \
-                KERBEROS and \
+                KERBEROS and OAUTH not in config.AUTHENTICATION_SOURCES and\
                 current_app.keyManager.get() is None and \
                 request.endpoint not in ('security.login', 'security.logout'):
             logout_user()
diff --git a/web/pgadmin/authenticate/__init__.py b/web/pgadmin/authenticate/__init__.py
index bc0868ddf..e350f5732 100644
--- a/web/pgadmin/authenticate/__init__.py
+++ b/web/pgadmin/authenticate/__init__.py
@@ -8,25 +8,28 @@
 ##########################################################################
 
 """A blueprint module implementing the Authentication."""
+from typing import Optional, Any
 
 import flask
-import pickle
-from flask import current_app, flash, Response, request, url_for,\
-    render_template
+
+from authlib.integrations.flask_client import OAuth
+from flask import current_app, flash, Response, request, url_for, \
+    render_template, Flask
 from flask_babelex import gettext
-from flask_security import current_user
-from flask_security.views import _security, _ctx
-from flask_security.utils import config_value, get_post_logout_redirect, \
+from flask_login import current_user
+from flask_security.views import _security
+from flask_security.utils import get_post_logout_redirect, \
     get_post_login_redirect, logout_user
 
 from flask import session
 
-import config
 from pgadmin.utils import PgAdminModule
 from pgadmin.utils.constants import KERBEROS
 from pgadmin.utils.csrf import pgCSRFProtect
 
-from .registry import AuthSourceRegistry
+from pgadmin.authenticate.registry import AuthSourceRegistry
+from pgadmin.utils.constants import OAUTH
+import config
 
 MODULE_NAME = 'authenticate'
 
@@ -35,7 +38,10 @@ class AuthenticateModule(PgAdminModule):
     def get_exposed_url_endpoints(self):
         return ['authenticate.login',
                 'authenticate.kerberos_login',
-                'authenticate.kerberos_logout']
+                'authenticate.kerberos_logout',
+                'authenticate.oauth_login',
+                'authenticate.oauth_authorize',
+                'authenticate.oauth_logout']
 
 
 blueprint = AuthenticateModule(MODULE_NAME, __name__, static_url_path='')
@@ -61,6 +67,57 @@ def kerberos_logout():
                                     ))
 
 
+oauth_obj = OAuth(Flask(__name__))
+
+
+@blueprint.route("/login/oauth_login", methods=['GET', 'POST'])
+@pgCSRFProtect.exempt
+def oauth_login():
+    """
+    Entry point for oauth source
+    """
+
+    oauth_obj.register(
+        name=getattr(config, 'NAME', None),
+        client_id=getattr(config, 'CLIENT_ID', None),
+        client_secret=getattr(config, 'CLIENT_SECRET', None),
+        access_token_url=getattr(config, 'TOKEN_URL', None),
+        access_token_params=None,
+        authorize_url=getattr(config, 'AUTHORIZATION_URL', None),
+        authorize_params=None,
+        api_base_url=getattr(config, 'API_BASE_URL', None),
+        userinfo_endpoint=getattr(config, 'USERINFO_ENDPOINT', None),
+        client_kwargs={'scope': 'openid email profile'},
+    )
+
+    session['oauth_client'] = oauth_obj.create_client(
+        getattr(config, 'NAME', None))
+    redirect_uri = url_for('authenticate.oauth_authorize', _external=True)
+    return session['oauth_client'].authorize_redirect(redirect_uri)
+
+
+@blueprint.route("/login/oauth_authorize")
+@pgCSRFProtect.exempt
+def oauth_authorize():
+    source = get_auth_sources(OAUTH)
+    status, user = source.authenticate()
+    login_status = source.login(user)
+    if login_status:
+        auth_obj = AuthSourceManager(None, config.AUTHENTICATION_SOURCES)
+        session['_auth_source_manager_obj'] = auth_obj.as_dict(source)
+        return flask.redirect(get_post_login_redirect())
+    source.logout()
+    return flask.redirect(get_post_login_redirect())
+
+
+@blueprint.route("/logout/oauth_logout", methods=['GET', 'POST'])
+@pgCSRFProtect.exempt
+def oauth_logout():
+    source = get_auth_sources(OAUTH)
+    source.logout()
+    return flask.redirect(get_post_login_redirect())
+
+
 @blueprint.route('/login', endpoint='login', methods=['GET', 'POST'])
 def login():
     """
@@ -70,7 +127,6 @@ def login():
     form = _security.login_form()
     auth_obj = AuthSourceManager(form, config.AUTHENTICATION_SOURCES)
     session['_auth_source_manager_obj'] = None
-
     # Validate the user
     if not auth_obj.validate():
         for field in form.errors:
@@ -85,14 +141,13 @@ def login():
         status, msg = auth_obj.login()
         current_auth_obj = auth_obj.as_dict()
         if not status:
-            if current_auth_obj['current_source'] ==\
+            if current_auth_obj['current_source'] == \
                     KERBEROS:
                 return flask.redirect('{0}?next={1}'.format(url_for(
                     'authenticate.kerberos_login'), url_for('browser.index')))
 
             flash(gettext(msg), 'danger')
             return flask.redirect(get_post_logout_redirect())
-
         session['_auth_source_manager_obj'] = current_auth_obj
         return flask.redirect(get_post_login_redirect())
 
@@ -103,9 +158,10 @@ def login():
     return response
 
 
-class AuthSourceManager():
+class AuthSourceManager:
     """This class will manage all the authentication sources.
      """
+
     def __init__(self, form, sources):
         self.form = form
         self.auth_sources = sources
@@ -113,11 +169,15 @@ class AuthSourceManager():
         self.source_friendly_name = None
         self.current_source = None
 
-    def as_dict(self):
+    def as_dict(self, source=None):
         """
         Returns the dictionary object representing this object.
         """
 
+        if source:
+            self.source_friendly_name = source.get_friendly_name()
+            self.current_source = source.get_source_name()
+
         res = dict()
         res['source_friendly_name'] = self.source_friendly_name
         res['auth_sources'] = self.auth_sources
@@ -165,7 +225,9 @@ class AuthSourceManager():
                 source.get_source_name())
 
             if self.form.data['email'] and self.form.data['password'] and \
-                    source.get_source_name() == KERBEROS:
+                source.get_source_name() == KERBEROS:
+                continue
+            if source.get_source_name() == OAUTH:
                 continue
 
             status, msg = source.authenticate(self.form)
diff --git a/web/pgadmin/authenticate/oauth.py b/web/pgadmin/authenticate/oauth.py
new file mode 100644
index 000000000..3624589a2
--- /dev/null
+++ b/web/pgadmin/authenticate/oauth.py
@@ -0,0 +1,67 @@
+import config
+import requests
+from flask import current_app
+from flask_babelex import gettext
+from flask_security import login_user, logout_user
+from pgadmin.utils.constants import OAUTH
+from pgadmin.tools import user_management
+from pgadmin.model import User
+
+from pgadmin.authenticate.internal import BaseAuthentication
+
+from web.pgadmin.utils import session
+from flask import session
+
+
+class OAuthAuthentication(BaseAuthentication):
+    """OAuth Authentication Class"""
+
+    def get_source_name(self):
+        return OAUTH
+
+    def get_friendly_name(self):
+        return gettext("oauth")
+
+    def validate(self, form):
+        return True
+
+    def login(self, user_info):
+        user = User.query.filter_by(email=user_info['email']).first()
+
+        if user is None:
+            current_app.logger.exception(
+                self.messages('USER_DOES_NOT_EXIST'))
+            return False, self.messages('USER_DOES_NOT_EXIST')
+        session.permanent = True
+        return login_user(user)
+
+    def logout(self):
+        resp = requests.post(
+            config.REVOKE_URL,
+            params={"token": session['token']['access_token']},
+            headers={"Content-Type": "application/x-www-form-urlencoded"}
+        )
+
+        if not resp.ok:
+            current_app.logger.exception(
+                "The current user could not be logged out : %s" %
+                resp.text)
+        logout_user()
+
+    def authenticate(self):
+        session['token'] = session['oauth_client'].authorize_access_token()
+        resp = session['oauth_client'].get('userinfo').json()
+        user = User.query.filter_by(email=resp['email']).first()
+        return self.__auto_create_user(user, resp)
+
+    def __auto_create_user(self, user, resp):
+        if config.OAUTH_AUTO_CREATE_USER:
+            if not user:
+                return user_management.create_user({
+                    'username': resp['name'],
+                    'email': resp['email'],
+                    'role': 2,
+                    'active': True,
+                    'auth_source': OAUTH
+                })
+        return True, resp
diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py
index 5fc7de64f..95be67cc0 100644
--- a/web/pgadmin/browser/__init__.py
+++ b/web/pgadmin/browser/__init__.py
@@ -52,6 +52,8 @@ from pgadmin.model import User
 from pgadmin.utils.constants import MIMETYPE_APP_JS, PGADMIN_NODE,\
     INTERNAL, KERBEROS
 
+from web.pgadmin.utils.constants import OAUTH
+
 try:
     from flask_security.views import default_render_json
 except ImportError as e:
@@ -604,12 +606,13 @@ class BrowserPluginModule(PgAdminModule):
 
 
 def _get_logout_url():
-    if config.SERVER_MODE and\
-            session['_auth_source_manager_obj']['current_source'] == \
-            KERBEROS:
-        return '{0}?next={1}'.format(url_for(
-            'authenticate.kerberos_logout'), url_for(BROWSER_INDEX))
-
+    if config.SERVER_MODE:
+        if session['_auth_source_manager_obj']['current_source'] == KERBEROS:
+            return '{0}?next={1}'.format(url_for(
+                'authenticate.kerberos_logout'), url_for(BROWSER_INDEX))
+        elif session['_auth_source_manager_obj']['current_source'] == OAUTH:
+            return '{0}?next={1}'.format(url_for(
+                'authenticate.oauth_logout'), url_for(BROWSER_INDEX))
     return '{0}?next={1}'.format(
         url_for('security.logout'), url_for(BROWSER_INDEX))
 
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py
index 13a7e06b9..bc0bd4611 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -1069,7 +1069,8 @@ class ServerNode(PGChildNodeView):
                 tunnel_username=data.get('tunnel_username', None),
                 tunnel_authentication=data.get('tunnel_authentication', 0),
                 tunnel_identity_file=data.get('tunnel_identity_file', None),
-                shared=data.get('shared', None)
+                shared=data.get('shared', None),
+                passfile=data.get('passfile', None)
             )
             db.session.add(server)
             db.session.commit()
@@ -1572,7 +1573,11 @@ class ServerNode(PGChildNodeView):
             sid: Server id
         """
         try:
-            data = json.loads(request.form['data'], encoding='utf-8')
+            if request.form and request.form['data']:
+                data = json.loads(request.form['data'], encoding='utf-8')
+            else:
+                data = json.loads(request.data, encoding='utf-8')
+
             crypt_key = get_crypt_key()[1]
 
             # Fetch Server Details
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/functions/ppas/sql/9.5_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/functions/ppas/sql/9.5_plus/create.sql
index caad0bfad..d79183996 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/functions/ppas/sql/9.5_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/functions/ppas/sql/9.5_plus/create.sql
@@ -17,7 +17,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
     RETURNS{% if data.proretset and (data.prorettypename.startswith('SETOF ') or data.prorettypename.startswith('TABLE')) %} {{ data.prorettypename }} {% elif data.proretset %} SETOF {{ data.prorettypename }}{% else %} {{ data.prorettypename }}{% endif %}
 
     LANGUAGE {{ data.lanname|qtLiteral }}
-    {% if data.provolatile %}{% if data.provolatile == 'i' %}IMMUTABLE{% elif data.provolatile == 'STABLE' %}STABLE{% else %}VOLATILE{% endif %} {% endif %}{% if data.proleakproof %}LEAKPROOF {% endif %}
+    {% if data.provolatile %}{% if data.provolatile == 'i' %}IMMUTABLE{% elif data.provolatile == 's' %}STABLE{% else %}VOLATILE{% endif %} {% endif %}{% if data.proleakproof %}LEAKPROOF {% endif %}
 {% if data.proisstrict %}STRICT {% endif %}
 {% if data.prosecdef %}SECURITY DEFINER {% endif %}
 {% if data.proiswindow %}WINDOW{% endif %}{% if data.procost %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/create.sql
index f6f62887c..2ba16e518 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/create.sql
@@ -18,10 +18,10 @@ LANGUAGE {{ data.lanname|qtLiteral }}{% if data.prosecdef %}
 
     SECURITY DEFINER {% endif %}
 {% if data.lanname == 'edbspl' %}
-{{ data.provolatile }} {% if data.proleakproof %}LEAKPROOF {% endif %}
+{% if data.provolatile %}{% if data.provolatile == 'i' %}IMMUTABLE{% elif data.provolatile == 's' %}STABLE{% else %}VOLATILE{% endif %} {% endif %}{% if data.proleakproof %}LEAKPROOF {% endif %}
 {% if data.proisstrict %}STRICT {% endif %}
 {% if data.proparallel and (data.proparallel == 'r' or data.proparallel == 's' or data.proparallel == 'u') %}
-{% if data.proparallel == 'r' %} PARALLEL RESTRICTED{% elif data.proparallel == 's' %} PARALLEL SAFE {% elif data.proparallel == 'u' %} PARALLEL UNSAFE{% endif %}{% endif %}{% if data.procost %}
+{% if data.proparallel == 'r' %}PARALLEL RESTRICTED{% elif data.proparallel == 's' %}PARALLEL SAFE{% elif data.proparallel == 'u' %}PARALLEL UNSAFE{% endif %} {% endif %}{% if data.procost %}
 
     COST {{data.procost}}{% endif %}{% if data.prorows and (data.prorows | int) > 0 %}
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/update.sql
index 43836ca50..d175db1c4 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/update.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedures/ppas/sql/11_plus/update.sql
@@ -22,7 +22,7 @@ CREATE OR REPLACE PROCEDURE {{ conn|qtIdent(o_data.pronamespace, name) }}({% if
     LANGUAGE {{ data.lanname|qtLiteral }} {% else %}
     LANGUAGE {{ o_data.lanname|qtLiteral }}
     {% endif %}
-{{ data.provolatile }}
+{% if 'provolatile' in data and data.provolatile %}{{ data.provolatile }} {% elif 'provolatile' not in data and o_data.provolatile %}{{ o_data.provolatile }} {% endif %}
 {% if ('prosecdef' in data and data.prosecdef) or ('prosecdef' not in data and o_data.prosecdef) %}SECURITY DEFINER{% endif %}
 {% if data.lanname == 'edbspl' or (o_data.lanname == 'edbspl' and not 'lanname' in data ) %}
 {% if ('proleakproof' in data and data.proleakproof) or ('proleakproof' not in data and o_data.proleakproof) %} LEAKPROOF{% else %} NOT LEAKPROOF{% endif %}
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
index 855fba055..9a1262d34 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
+++ b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
@@ -186,6 +186,56 @@
       "expected_data": {
         "status_code": 200
       }
+    },
+    {
+      "name": "Add server with ssl",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "sslcert": "postgres.crt",
+        "sslkey": "postgres.key",
+        "sslrootcert": "root.crt",
+        "sslmode": "prefer",
+        "sslcompression": true,
+        "sslcrl": "postgres.crl"
+
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Add server with advanced properties",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "passfile": "test.pgpass",
+        "hostaddr": "127.0.0.1"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Add server with background/foreground color",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "fgcolor":"#FF9900",
+        "bgcolor": "#00FF00"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
     }
   ],
   "is_password_saved": [
@@ -422,6 +472,56 @@
         "status_code": 200
       }
     },
+    {
+      "name": "connect to a server using password (invalid user)",
+      "url": "/browser/server/connect/",
+      "is_positive_test": true,
+      "connect": true,
+      "invalid_user": true,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.browser.server_groups.servers.User",
+        "return_value": "None"
+      },
+      "expected_data": {
+        "status_code": 401
+      }
+    },
+    {
+      "name": "connect to a server using password (invalid server username)",
+      "url": "/browser/server/connect/",
+      "is_positive_test": true,
+      "connect": true,
+      "invalid_server_username": true,
+      "mocking_required": true,
+      "mock_data": {
+        "id": 1,
+        "name": "test_mock_server",
+        "username": "None",
+        "shared": true,
+        "service": false,
+        "user_id": "",
+        "function_name": "pgadmin.browser.server_groups.servers.Server",
+        "return_value": "None"
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Fail check recovery state on connected server",
+      "url": "/browser/server/connect/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "recovery_state": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error while get recovery_state for server.')"
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    },
     {
       "name": "Disconnect server test",
       "url": "/browser/server/connect/",
@@ -470,6 +570,152 @@
       }
     }
   ],
+  "connect_ssh_mock": [
+    {
+      "name": "Try to connect server using ssh tunnel password",
+      "url": "/browser/server/connect/",
+      "is_positive_test": true,
+      "connect": true,
+      "ssh_tunnel_connect": true,
+      "mocking_required": false,
+      "mock_data": {
+        "id": 1,
+        "name": "test_mock_server",
+        "username": "postgre1",
+        "use_ssh_tunnel": 1,
+        "tunnel_host": "127.0.0.1",
+        "tunnel_port": 22,
+        "tunnel_username": "user",
+        "tunnel_authentication": 1,
+        "tunnel_password": "user123",
+        "tunnel_identity_file": "pkey_rsa",
+        "service": null,
+        "server_info": {
+          "id": 1,
+          "name": "test_mock_server",
+          "username": "postgres",
+          "passfile": false
+        },
+        "user_info": {
+          "id": 1,
+          "username": "postgres",
+          "password": "1234"
+        },
+        "manager": {
+          "server_type": "pg",
+          "password": "my_postgres",
+          "sversion": 100000,
+          "connection_connect_return_value": "psycopg2.OperationalError()"
+        }
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Try to connect server without ssh tunnel password",
+      "url": "/browser/server/connect/",
+      "is_positive_test": true,
+      "connect": true,
+      "ssh_tunnel_connect": true,
+      "mocking_required": false,
+      "mock_data": {
+        "id": 1,
+        "name": "test_mock_server",
+        "username": "postgre1",
+        "use_ssh_tunnel": 1,
+        "tunnel_host": "127.0.0.1",
+        "tunnel_port": 22,
+        "tunnel_username": "user",
+        "tunnel_authentication": 1,
+        "tunnel_password": "",
+        "tunnel_identity_file": "pkey_rsa",
+        "service": null,
+        "server_info": {
+          "id": 1,
+          "name": "test_mock_server",
+          "username": "postgres",
+          "passfile": false
+        },
+        "user_info": {
+          "id": 1,
+          "username": "postgres",
+          "password": "1234"
+        },
+        "manager": {
+          "server_type": "pg",
+          "password": "my_postgres",
+          "sversion": 100000,
+          "connection_connect_return_value": "OperationalError()"
+        }
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ],
+  "wal_replay_server": [
+    {
+      "name": "Pause the wal replay recovery control",
+      "url": "/browser/server/wal_replay/",
+      "is_positive_test": true,
+      "mocking_required": true,
+      "pause": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar",
+        "return_value": "(True, {'rows': []})"
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Resume the wal replay recovery control",
+      "url": "/browser/server/wal_replay/",
+      "is_positive_test": true,
+      "mocking_required": true,
+      "pause": false,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar",
+        "return_value": "(True, {'rows': []})"
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while wal replay pause",
+      "url": "/browser/server/wal_replay/",
+      "is_positive_test": false,
+      "pause": true,
+      "test_data": {
+        "comment": "PLACE_HOLDER",
+        "id": "PLACE_HOLDER",
+        "is_password_saved": false
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Error while wal replay resume",
+      "url": "/browser/server/wal_replay/",
+      "is_positive_test": false,
+      "pause": false,
+      "test_data": {
+        "comment": "PLACE_HOLDER",
+        "id": "PLACE_HOLDER",
+        "is_password_saved": false
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
   "delete_server": [
     {
       "name": "Delete a server URL",
@@ -624,21 +870,6 @@
         "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/",
@@ -687,6 +918,89 @@
       "expected_data": {
         "status_code": 200
       }
+    },
+    {
+      "name": "update ssl properties of server",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "sslcert": "postgres_01.crt",
+        "sslkey": "postgres_01.key",
+        "sslrootcert": "root_01.crt",
+        "sslmode": "allow",
+        "sslcompression": false,
+        "sslcrl": "postgres.crl"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "update advanced properties of server",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "passfile": "test_01.pgpass",
+        "hostaddr": "127.0.0.1"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "remove ssl properties from server",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "sslcert": "",
+        "sslkey": "",
+        "sslrootcert": "",
+        "sslmode": "prefer",
+        "sslcompression": false,
+        "sslcrl": ""
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "remove advanced properties from server",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "passfile": "",
+        "hostaddr": ""
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Update server with background/foreground color",
+      "url": "/browser/server/obj/",
+      "is_positive_test": true,
+      "owner_server": true,
+      "test_data": {
+        "fgcolor":"#B6D7A8",
+        "bgcolor": "#0C343D"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
     }
   ],
   "update_shared_server": [
@@ -858,5 +1172,45 @@
         "status_code": 200
       }
     }
+  ],
+  "change_password": [
+    {
+      "name": "Change password",
+      "url": "/browser/server/change_password/",
+      "is_positive_test": true,
+      "mocking_required": true,
+      "mock_data": {
+        "server_info": {
+          "sid": 1,
+          "name": "test_mock_server",
+          "username": "postgres",
+          "password": "post123",
+          "passfile": false
+        },
+        "user_info": {
+          "id": 1,
+          "username": "postgres",
+          "password": "1234"
+        },
+        "manager": {
+          "server_type": "pg",
+          "password": "my_postgres",
+          "sversion": 100000,
+          "connection_execute_scalar_return_value": "(True, {'rows': []})"
+        }
+      },
+      "test_data": {
+        "form_data" : {
+          "user_name": "my_postgres",
+          "password": "my_postgres",
+          "newPassword": "my_postgres1",
+          "confirmPassword": "my_postgres1"
+        }
+      },
+      "expected_data": {
+        "status_code": 200,
+        "update_session": true
+      }
+    }
   ]
 }
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
index ee6bf9f92..6f6165e37 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
@@ -62,6 +62,30 @@ class AddServerTest(BaseTestGenerator):
             self.server['connect_now'] = self.test_data['connect_now']
             self.server['password'] = self.server['db_password']
 
+        # SSL properties
+        if 'sslcert' in self.test_data:
+            self.server['sslcert'] = self.test_data['sslcert']
+        if 'sslkey' in self.test_data:
+            self.server['sslkey'] = self.test_data['sslkey']
+        if 'sslrootcert' in self.test_data:
+            self.server['sslrootcert'] = self.test_data['sslrootcert']
+        if 'sslmode' in self.test_data:
+            self.server['sslmode'] = self.test_data['sslmode']
+        if 'sslcompression' in self.test_data:
+            self.server['sslcompression'] = self.test_data['sslcompression']
+
+        # Advanced tab properties
+        if 'passfile' in self.test_data:
+            self.server['passfile'] = self.test_data['passfile']
+        if 'hostaddr' in self.test_data:
+            self.server['hostaddr'] = self.test_data['hostaddr']
+
+        # Background/Foreground color
+        if 'fgcolor' in self.test_data:
+            self.server['fgcolor'] = self.test_data['fgcolor']
+        if 'bgcolor' in self.test_data:
+            self.server['bgcolor'] = self.test_data['bgcolor']
+
         if self.is_positive_test:
             if hasattr(self, 'with_save'):
                 self.server['save_password'] = self.with_save
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
index d1e2ddf66..86485c0c7 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
@@ -12,6 +12,7 @@ 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 unittest.mock import patch, MagicMock
 
 
 class ServersConnectTestCase(BaseTestGenerator):
@@ -25,18 +26,18 @@ class ServersConnectTestCase(BaseTestGenerator):
 
     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
+        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 hasattr(self, 'with_password') and self.with_password:
+            self.server.tunnel_authentication = 0
         else:
-            self.server['tunnel_authentication'] = 1
-            self.server['tunnel_identity_file'] = 'pkey_rsa'
+            self.server.tunnel_authentication = 1
+            self.server.tunnel_identity_file = 'pkey_rsa'
 
-        if self.save_password:
-            self.server['tunnel_password'] = '123456'
+        if hasattr(self, 'save_password') and self.save_password:
+            self.server.tunnel_password = '123456'
 
     def setUp(self):
         """This function add the server to test the GET API"""
@@ -84,7 +85,49 @@ class ServersConnectTestCase(BaseTestGenerator):
                     utils.SERVER_GROUP,
                     self.server_id)
                 self.server['password'] = self.server['db_password']
-                response = self.connect_to_server(url)
+
+                if self.mocking_required:
+                    if hasattr(self, "invalid_user"):
+                        with patch(self.mock_data['function_name'],
+                                   side_effect=[eval(self.mock_data[
+                                       "return_value"])]) as user_mock:
+
+                            user_mock_result = user_mock.query.filter_by.\
+                                return_value
+                            user_mock_result.first.return_value = None
+                            response = self.connect_to_server(url)
+
+                    elif hasattr(self, "invalid_server_username"):
+                        with patch(self.mock_data['function_name'],
+                                   side_effect=[eval(self.mock_data[
+                                       "return_value"])]) as server_mock:
+
+                            class TestMockServer():
+                                def __init__(self, name, id, username, shared,
+                                             service):
+                                    self.name = name
+                                    self.id = id
+                                    self.username = username
+                                    self.shared = shared
+                                    self.service = service
+                                    self.user_id = id
+
+                            mock_server_obj = TestMockServer(
+                                self.mock_data['name'],
+                                self.mock_data['id'],
+                                eval(self.mock_data['username']),
+                                self.mock_data['shared'],
+                                self.mock_data['service']
+                            )
+
+                            server_mock_result = server_mock.query.filter_by.\
+                                return_value
+                            server_mock_result.first.return_value = \
+                                mock_server_obj
+
+                            response = self.connect_to_server(url)
+                else:
+                    response = self.connect_to_server(url)
             elif hasattr(self, 'restore_point') or hasattr(self,
                                                            'change_password'):
                 connect_url = '/browser/server/connect/{0}/{1}'.format(
@@ -96,6 +139,29 @@ class ServersConnectTestCase(BaseTestGenerator):
 
                 self.connect_to_server(connect_url)
                 response = self.add_server_details(url)
+            elif hasattr(self, "recovery_state") and self.recovery_state:
+                with patch('pgadmin.browser.server_groups.'
+                           'servers.get_driver') as get_driver_mock:
+
+                    self.manager = MagicMock()
+                    get_driver_mock.return_value = MagicMock(
+                        connection_manager=MagicMock(
+                            execute_dict=MagicMock(
+                                return_value=self.manager.connection),
+                            return_value=self.manager)
+                    )
+                    self.manager.version = 10
+
+                    connection_mock_result = \
+                        self.manager.connection.return_value
+                    self.manager.connection.connected.side_effect = True
+
+                    connection_mock_result.execute_dict.side_effect = \
+                        [eval(self.mock_data["return_value"])]
+
+                    response = self.get_server_connection(server_id)
+                    self.assertEquals(response.status_code,
+                                      self.expected_data["status_code"])
             else:
                 response = self.get_server_connection(server_id)
 
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_check_recovery_code.py b/web/pgadmin/browser/server_groups/servers/tests/test_check_recovery_code.py
new file mode 100644
index 000000000..1583ca15f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_check_recovery_code.py
@@ -0,0 +1,63 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+from regression import parent_node_dict
+from unittest.mock import patch
+import json
+
+
+class CheckRecoveryCodeTestCase(BaseTestGenerator):
+    """
+    This class will try to test cover the wal_reply code.
+    """
+
+    scenarios = utils.generate_scenarios('wal_replay_server',
+                                         servers_utils.test_cases)
+
+    def resume_wal_replay(self):
+        return self.tester.put(
+            self.url + str(utils.SERVER_GROUP) + '/' + str(self.server_id))
+
+    def pause_wal_replay(self):
+        return self.tester.delete(
+            self.url + str(utils.SERVER_GROUP) + '/' + str(self.server_id))
+
+    def runTest(self):
+
+        server_id = parent_node_dict["server"][-1]["server_id"]
+        if not server_id:
+            raise Exception("Server not found to test GET API")
+
+        if self.mocking_required:
+
+            with patch(self.mock_data['function_name'],
+                       side_effect=[eval(self.mock_data['return_value'])]):
+                response = self.run_test_cases()
+
+                res = json.loads(response.data.decode('utf-8'))
+                self.assertEqual(res['data']['in_recovery'], True)
+                self.assertEqual(res['data']['wal_pause'], self.pause)
+                self.assertEquals(response.status_code,
+                                  self.expected_data["status_code"])
+        else:
+            response = self.run_test_cases()
+            self.assertEquals(response.status_code,
+                              self.expected_data["status_code"])
+
+    def run_test_cases(self):
+
+        if hasattr(self, 'pause') and self.pause:
+            response = self.pause_wal_replay()
+        else:
+            response = self.resume_wal_replay()
+
+        return response
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_check_ssh_mock_connect.py b/web/pgadmin/browser/server_groups/servers/tests/test_check_ssh_mock_connect.py
new file mode 100644
index 000000000..4bed4d357
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_check_ssh_mock_connect.py
@@ -0,0 +1,99 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+from unittest.mock import patch, MagicMock
+import json
+from psycopg2 import OperationalError
+
+
+class ServersSSHConnectTestCase(BaseTestGenerator):
+    """
+    This class will try to mock connect server with ssh credentials.
+    """
+
+    scenarios = utils.generate_scenarios('connect_ssh_mock',
+                                         servers_utils.test_cases)
+
+    def connect_to_server(self, url, server):
+        return self.tester.post(
+            url,
+            data=json.dumps(server),
+            content_type='html/json'
+        )
+
+    @patch('pgadmin.browser.server_groups.servers.get_driver')
+    @patch('pgadmin.browser.server_groups.servers.Server')
+    def runTest(self, server_mock, get_driver_mock):
+
+        if self.mock_data is not None and \
+                self.mock_data['use_ssh_tunnel'] == 1:
+
+            self.manager = MagicMock()
+            get_driver_mock.return_value = MagicMock(
+                connection_manager=MagicMock(
+                    execute_scalar=MagicMock(
+                        return_value=self.manager.connection),
+                    return_value=self.manager)
+            )
+            self.manager.password = self.mock_data['manager']['password']
+            self.manager.server_type = self.mock_data['manager']['server_type']
+            self.manager.sversion = self.mock_data['manager']['sversion']
+
+            self.manager.connection().connect.side_effect = \
+                MagicMock(side_effect=OperationalError())
+
+            url = self.url + '{0}/{1}'.format(utils.SERVER_GROUP, 1)
+
+            class TestMockServer():
+                def __init__(self, name, id, username, use_ssh_tunnel,
+                             tunnel_host, tunnel_port,
+                             tunnel_username, tunnel_authentication,
+                             tunnel_identity_file, tunnel_password, service):
+                    self.name = name
+                    self.id = id
+                    self.username = username
+
+                    self.use_ssh_tunnel = use_ssh_tunnel
+                    self.tunnel_host = tunnel_host
+                    self.tunnel_port = tunnel_port
+                    self.tunnel_username = tunnel_username
+                    self.tunnel_authentication = \
+                        tunnel_authentication
+                    self.tunnel_identity_file = \
+                        tunnel_identity_file
+                    self.tunnel_password = tunnel_password
+                    self.service = service
+                    self.shared = None
+
+            mock_server_obj = TestMockServer(
+                self.mock_data['name'],
+                self.mock_data['id'],
+                self.mock_data['username'],
+                self.mock_data['use_ssh_tunnel'],
+                self.mock_data['tunnel_host'],
+                self.mock_data['tunnel_port'],
+                self.mock_data['tunnel_username'],
+                self.mock_data['tunnel_authentication'],
+                self.mock_data['tunnel_identity_file'],
+                self.mock_data['tunnel_password'],
+                self.mock_data['service'],
+            )
+
+            server_mock_result = server_mock.query.filter_by.return_value
+            server_mock_result.first.return_value = mock_server_obj
+
+            if self.mock_data['tunnel_password'] == '':
+                del self.server['tunnel_password']
+
+            response = self.connect_to_server(url, self.server)
+
+            self.assertEqual(response.status_code, 500)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_password_change.py b/web/pgadmin/browser/server_groups/servers/tests/test_password_change.py
new file mode 100644
index 000000000..f5ff7d14d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_password_change.py
@@ -0,0 +1,104 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, 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
+from unittest.mock import patch, MagicMock
+
+
+class DBPasswordChange(BaseTestGenerator):
+    """ This class will test the change password functionality. """
+
+    scenarios = utils.generate_scenarios('change_password',
+                                         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)
+
+    @patch('pgadmin.browser.server_groups.servers.render_template')
+    @patch('pgadmin.browser.server_groups.servers.pqencryptpassword')
+    @patch('pgadmin.browser.server_groups.servers.decrypt')
+    @patch('pgadmin.browser.server_groups.servers.get_driver')
+    @patch('pgadmin.browser.server_groups.servers.db')
+    @patch('pgadmin.browser.server_groups.servers.Server')
+    @patch('pgadmin.browser.server_groups.servers.User')
+    @patch('pgadmin.browser.server_groups.servers.current_user')
+    def runTest(self, current_user_mock, user_mock, server_mock, db_mock,
+                get_driver_mock, decrypt_mock, pqencryptpassword_mock,
+                render_template_mock):
+
+        current_user_mock.id = 1
+
+        self.manager = MagicMock()
+        get_driver_mock.return_value = MagicMock(
+            connection_manager=MagicMock(execute_scalar=MagicMock(
+                return_value=self.manager.connection),
+                return_value=self.manager)
+        )
+        self.manager.password = self.mock_data['manager']['password']
+        self.manager.server_type = self.mock_data['manager']['server_type']
+        self.manager.sversion = self.mock_data['manager']['sversion']
+        self.manager.connection().execute_scalar.return_value = \
+            eval(self.mock_data['manager']
+                 ['connection_execute_scalar_return_value'])
+
+        decrypt_mock.return_value = self.manager.password
+        pqencryptpassword_mock.return_value = self.manager.password
+
+        class TestMockServer():
+            def __init__(self, name, sid, password, passfile):
+                self.name = name
+                self.sid = sid
+                self.password = password
+                self.passfile = passfile
+
+        class TestUser():
+            def __init__(self, id, username, password):
+                self.id = id
+                self.username = username
+                self.password = password
+
+        db_mock.session.commit = MagicMock(return_value=True)
+
+        mock_server_obj = TestMockServer(
+            self.mock_data['server_info']['username'],
+            self.mock_data['server_info']['sid'],
+            self.mock_data['server_info']['password'],
+            self.mock_data['server_info']['passfile']
+        )
+        server_mock_result = server_mock.query.filter_by.return_value
+        server_mock_result.first.return_value = mock_server_obj
+
+        mock_user_obj = TestUser(self.mock_data['user_info']['id'],
+                                 self.mock_data['user_info']['username'],
+                                 self.mock_data['user_info']['password'])
+
+        user_mock_result = user_mock.query.filter_by.return_value
+        user_mock_result.first.return_value = mock_user_obj
+
+        """This function will execute the connect server APIs"""
+        response = self.tester.post(
+            self.url + str(1) + '/' + str(mock_server_obj.sid),
+            data=json.dumps(self.test_data['form_data']),
+            follow_redirects=True
+        )
+
+        self.assertEqual(response.status_code,
+                         self.expected_data['status_code'])
+
+        self.assertEquals(render_template_mock.called, True)
+        self.assertEquals(self.manager.update_session.called,
+                          self.expected_data['update_session'])
+        self.assertEqual(
+            self.manager.connection().pq_encrypt_password_conn.called, True)
diff --git a/web/pgadmin/browser/server_groups/tests/servers_group_test_data.json b/web/pgadmin/browser/server_groups/tests/servers_group_test_data.json
new file mode 100644
index 000000000..f0c35b7fb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/tests/servers_group_test_data.json
@@ -0,0 +1,45 @@
+{
+  "get_server_group_children": [
+    {
+      "name": "Get the all children of server group",
+      "url": "/browser/server_group/children/",
+      "is_positive_test": true,
+      "children": true,
+      "mocking_required": true,
+      "mock_data": {
+        "id": 1,
+        "name": "test_mock_server",
+        "username": "postgre1",
+        "use_ssh_tunnel": 1,
+        "tunnel_host": "127.0.0.1",
+        "tunnel_port": 22,
+        "tunnel_username": "user",
+        "tunnel_authentication": 1,
+        "tunnel_password": "user123",
+        "tunnel_identity_file": "pkey_rsa",
+        "service": null,
+        "fgcolor":"#B6D7A8",
+        "bgcolor": "#0C343D",
+        "servergroup_id": 5,
+        "server_owner": "admin"
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ],
+  "get_server_group": [
+    {
+      "name": "Check Server Group Node",
+      "url": "/browser/server_group/obj/",
+      "is_positive_test": true,
+      "children": true,
+      "mocking_required": false,
+      "mock_data": {
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ]
+}
diff --git a/web/pgadmin/browser/server_groups/tests/test_servers_groups_childrens.py b/web/pgadmin/browser/server_groups/tests/test_servers_groups_childrens.py
new file mode 100644
index 000000000..4f4d439c6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/tests/test_servers_groups_childrens.py
@@ -0,0 +1,84 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as cast_utils
+from regression.test_setup import config_data
+import json
+import config
+from unittest.mock import patch
+from regression.python_test_utils.test_utils import \
+    create_user_wise_test_client
+
+test_user_details = None
+if config.SERVER_MODE:
+    test_user_details = config_data['pgAdmin4_test_non_admin_credentials']
+
+
+class ServerGroupsChildren(BaseTestGenerator):
+    """
+    This class will fetch all children of server group by response code.
+    """
+
+    scenarios = utils.generate_scenarios('get_server_group_children',
+                                         cast_utils.test_cases)
+
+    def get_server(self, server_id):
+        return self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
+                               str(server_id),
+                               follow_redirects=True)
+
+    def setUp(self):
+
+        if config.SERVER_MODE is True:
+            self.server['shared'] = True
+            url = "/browser/server/obj/{0}/".format(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": response_data['node']['_id']}
+            utils.write_node_info("sid", server_dict)
+
+    def runTest(self):
+
+        if config.SERVER_MODE is True:
+            self.testServerGroupsForServerMode()
+        else:
+            self.testServerGroupsForDesktopMode()
+
+    @patch('pgadmin.browser.server_groups.servers.current_user')
+    @create_user_wise_test_client(test_user_details)
+    def testServerGroupsForServerMode(self, current_user_mock):
+
+        current_user_mock.id = 103040
+        self.server_group_id = config_data['server_group']
+
+        response = self.tester.get(self.url + str(self.server_group_id),
+                                   content_type='html/json')
+        self.assertTrue(response.status_code, 200)
+        response_data = json.loads(response.data.decode('utf8'))
+        self.assertTrue(response_data['success'], 1)
+
+    @patch('pgadmin.browser.server_groups.servers.current_user')
+    def testServerGroupsForDesktopMode(self, current_user_mock):
+
+        current_user_mock.id = 1
+        self.server_group_id = config_data['server_group']
+
+        response = self.tester.get(self.url + str(self.server_group_id),
+                                   content_type='html/json')
+        self.assertTrue(response.status_code, 200)
+        response_data = json.loads(response.data.decode('utf8'))
+        self.assertTrue(response_data['success'], 1)
diff --git a/web/pgadmin/browser/server_groups/tests/test_sg_get.py b/web/pgadmin/browser/server_groups/tests/test_sg_get.py
index cdfd3d76e..e5c680133 100644
--- a/web/pgadmin/browser/server_groups/tests/test_sg_get.py
+++ b/web/pgadmin/browser/server_groups/tests/test_sg_get.py
@@ -11,6 +11,8 @@ import json
 
 from pgadmin.utils.route import BaseTestGenerator
 from regression.test_setup import config_data
+from regression.python_test_utils import test_utils as utils
+from . import utils as cast_utils
 
 
 class SgNodeTestCase(BaseTestGenerator):
@@ -18,10 +20,8 @@ class SgNodeTestCase(BaseTestGenerator):
      This class will check available server groups in pgAdmin.
     """
 
-    scenarios = [
-        # Fetching the default url for server group node
-        ('Check Server Group Node', dict(url='/browser/server_group/obj/'))
-    ]
+    scenarios = utils.generate_scenarios('get_server_group',
+                                         cast_utils.test_cases)
 
     def runTest(self):
         """This function will check available server groups."""
diff --git a/web/pgadmin/browser/server_groups/tests/utils.py b/web/pgadmin/browser/server_groups/tests/utils.py
new file mode 100644
index 000000000..1a59cca4c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/tests/utils.py
@@ -0,0 +1,16 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Server Group helper utilities"""
+import os
+import json
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+with open(CURRENT_PATH + "/servers_group_test_data.json") as data_file:
+    test_cases = json.load(data_file)
diff --git a/web/pgadmin/messages.pot b/web/pgadmin/messages.pot
index 3d367f122..1a64857b0 100644
--- a/web/pgadmin/messages.pot
+++ b/web/pgadmin/messages.pot
@@ -149,6 +149,11 @@ msgstr ""
 msgid "ldap"
 msgstr ""
 
+#: pgadmin/authenticate/oauth.py:16
+#: pgadmin/templates/security/login_user.html:29
+msgid "Log in with oauth"
+msgstr ""
+
 #: pgadmin/authenticate/registry.py:50
 msgid "Authentication source '{0}' has not been implemented."
 msgstr ""
diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss
index 6a185471b..d990b5509 100644
--- a/web/pgadmin/static/scss/_pgadmin.style.scss
+++ b/web/pgadmin/static/scss/_pgadmin.style.scss
@@ -946,6 +946,9 @@ table.table-empty-rows{
   & .btn-login {
     background-color: $security-btn-color;
   }
+  & .btn-oauth {
+    background-color: $security-btn-color;
+  }
   & .user-language {
     & select{
       background-color: $color-primary;
diff --git a/web/pgadmin/templates/security/login_user.html b/web/pgadmin/templates/security/login_user.html
index 2e92d7b12..390120407 100644
--- a/web/pgadmin/templates/security/login_user.html
+++ b/web/pgadmin/templates/security/login_user.html
@@ -20,9 +20,14 @@
                 {% for key, lang in config.LANGUAGES.items() %}
                 <option value="{{key}}" {% if user_language == key %}selected{% endif %}>{{lang}}</option>
                 {% endfor %}
-             </select>
+            </select>
         </div>
     </div>
 </form>
 {% endif %}
+{% if "oauth" in config.AUTHENTICATION_SOURCES and config.AUTHENTICATION_SOURCES | length > 1 %}
+    <form action="{{ url_for('authenticate.oauth_login') }}" method="POST" name="login_oauth_form">
+        <button class="btn btn-primary btn-block btn-oauth" type="submit">{{ _('Log in with oauth') }}</button>
+    </form>
+{% endif %}
 {% endblock %}
diff --git a/web/pgadmin/tools/datagrid/static/js/show_data.js b/web/pgadmin/tools/datagrid/static/js/show_data.js
index e41978876..5a603aa1f 100644
--- a/web/pgadmin/tools/datagrid/static/js/show_data.js
+++ b/web/pgadmin/tools/datagrid/static/js/show_data.js
@@ -45,7 +45,6 @@ export function showDataGrid(
 
   let applicable_nodes = ['table', 'view', 'mview', 'foreign_table'];
   if (applicable_nodes.indexOf(node.getData()._type) === -1) {
-    alertify.error(gettext('This feature is not applicable to the selected object.'));
     return;
   }
 
diff --git a/web/pgadmin/utils/constants.py b/web/pgadmin/utils/constants.py
index 5fd942304..c635f9da5 100644
--- a/web/pgadmin/utils/constants.py
+++ b/web/pgadmin/utils/constants.py
@@ -52,7 +52,9 @@ ERROR_FETCHING_DATA = gettext('Unable to fetch data.')
 INTERNAL = 'internal'
 LDAP = 'ldap'
 KERBEROS = 'kerberos'
+OAUTH = "oauth"
 
 SUPPORTED_AUTH_SOURCES = [INTERNAL,
                           LDAP,
-                          KERBEROS]
+                          KERBEROS,
+                          OAUTH]
diff --git a/web/pgadmin/utils/master_password.py b/web/pgadmin/utils/master_password.py
index 629eec941..9457ba91e 100644
--- a/web/pgadmin/utils/master_password.py
+++ b/web/pgadmin/utils/master_password.py
@@ -5,6 +5,7 @@ from pgadmin.model import db, User, Server
 from pgadmin.utils.crypto import encrypt, decrypt
 from pgadmin.utils.constants import KERBEROS
 
+from web.pgadmin.utils.constants import OAUTH
 
 MASTERPASS_CHECK_TEXT = 'ideas are bulletproof'
 
@@ -27,17 +28,20 @@ def get_crypt_key():
 
     # if desktop mode and master pass disabled then use the password hash
     if not config.MASTER_PASSWORD_REQUIRED \
-            and not config.SERVER_MODE:
+        and not config.SERVER_MODE:
         return True, current_user.password
     # if desktop mode and master pass enabled
     elif config.MASTER_PASSWORD_REQUIRED \
-            and not config.SERVER_MODE and enc_key is None:
+        and not config.SERVER_MODE and enc_key is None:
         return False, None
-    elif config.SERVER_MODE and \
-            session['_auth_source_manager_obj']['source_friendly_name']\
-            == KERBEROS:
-        return True, session['kerberos_key'] if 'kerberos_key' in session \
-            else None
+    elif config.SERVER_MODE:
+        if session['_auth_source_manager_obj']['source_friendly_name'] \
+                == KERBEROS:
+            return True, session['kerberos_key'] if 'kerberos_key' \
+                                                    in session else None
+        elif session['_auth_source_manager_obj']['source_friendly_name'] \
+                == OAUTH:
+            return False, None
     else:
         return True, enc_key
 
@@ -118,7 +122,7 @@ def process_masterpass_disabled():
     :param conn_data: connection manager copy from session if any
     """
     if not config.SERVER_MODE and not config.MASTER_PASSWORD_REQUIRED \
-            and current_user.masterpass_check is not None:
+        and current_user.masterpass_check is not None:
         cleanup_master_password()
         return True
 

Reply via email to