public inbox for [email protected]
help / color / mirror / Atom feedFrom: Florian Sabonchi <[email protected]>
To: [email protected]
Subject: OAUTH2 implementation
Date: Tue, 30 Mar 2021 16:09:06 +0200
Message-ID: <[email protected]> (raw)
Hello in this patch I have implemented oauth2
Attachments:
[text/x-patch] oauth2.diff (62.2K, 2-oauth2.diff)
download | inline diff:
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__))
+
+
[email protected]("/login/oauth_login", methods=['GET', 'POST'])
[email protected]
+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)
+
+
[email protected]("/login/oauth_authorize")
[email protected]
+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())
+
+
[email protected]("/logout/oauth_logout", methods=['GET', 'POST'])
[email protected]
+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
view thread (2+ 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]
Subject: Re: OAUTH2 implementation
In-Reply-To: <[email protected]>
* 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