public inbox for [email protected]
help / color / mirror / Atom feedFrom: Khushboo Vashi <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication.
Date: Tue, 12 Oct 2021 10:43:07 +0530
Message-ID: <CAFOhELdQXmy3v3wnQYuxNDrz2+q48jzgr9RgSXt4gQ0LY81fBA@mail.gmail.com> (raw)
Hi,
Please find the attached patch for #6657 - Support for REMOTE_USER as
Authentication.
The initial patch was provided by *Tom Schreiber,* I have made the
necessary changes to work with pgAdmin and also added test cases and
documentation.
Please commit the patch on *Tom Schreiber's *name.
This patch also includes the fix for the RM #6719 - OAuth2 integration
redirect issue
Thanks,
Khushboo
Attachments:
[application/octet-stream] RM_6657.patch (17.0K, 3-RM_6657.patch)
download | inline diff:
diff --git a/docs/en_US/getting_started.rst b/docs/en_US/getting_started.rst
index 700e8d48b..92967847e 100644
--- a/docs/en_US/getting_started.rst
+++ b/docs/en_US/getting_started.rst
@@ -39,6 +39,7 @@ Mode is pre-configured for security.
ldap
kerberos
oauth2
+ webserver
.. note:: Pre-compiled and configured installation packages are available for
diff --git a/docs/en_US/webserver.rst b/docs/en_US/webserver.rst
new file mode 100644
index 000000000..abd0cf6c4
--- /dev/null
+++ b/docs/en_US/webserver.rst
@@ -0,0 +1,44 @@
+.. _webserver:
+
+********************************************
+`Enabling Webserver Authentication`:index:
+********************************************
+
+To configure Webserver authentication, you must setup your webserver
+with any authentication plug-in (such as Shibboleth, HTTP BASIC auth)
+as long as it sets the REMOTE_USER environment variable.
+To enable Webserver authentication for pgAdmin, you must configure the Webserver
+settings in the *config_local.py* or *config_system.py* file (see the
+:ref:`config.py <config_py>` documentation) on the system where pgAdmin is
+installed in Server mode. You can copy these settings from *config.py* file
+and modify the values for the following parameters:
+
+
+.. csv-table::
+ :header: "**Parameter**", "**Description**"
+ :class: longtable
+ :widths: 35, 55
+
+ "AUTHENTICATION_SOURCES", "The default value for this parameter is *internal*.
+ To enable OAUTH2 authentication, you must include *webserver* in the list of values
+ for this parameter. you can modify the value as follows:
+
+ * [‘webserver’]: pgAdmin will use only Webserver authentication.
+
+ * [‘webserver’, ‘internal’]: pgAdmin will first try to authenticate the user
+ through webserver. If that authentication fails, then it will return back
+ to the login dialog where you need to provide internal pgAdmin user
+ credentials for authentication."
+ "WEBSERVER_AUTO_CREATE_USER", "Set the value to *True* if you want to automatically
+ create a pgAdmin user corresponding to a successfully authenticated Webserver user.
+ Please note that password is not stored in the pgAdmin database."
+
+
+Master Password
+===============
+
+In the multi user mode, pgAdmin uses user's login password to encrypt/decrypt the PostgreSQL server password.
+In the Webserver authentication, the pgAdmin does not store the user's password, so we need an encryption key to store
+the PostgreSQL server password.
+To accomplish this, set the configuration parameter MASTER_PASSWORD to *True*, so upon setting the master password,
+it will be used as an encryption key while storing the password. If it is False, the server password can not be stored.
diff --git a/web/config.py b/web/config.py
index 7a1f4ab1f..db313273e 100644
--- a/web/config.py
+++ b/web/config.py
@@ -570,7 +570,8 @@ ENHANCED_COOKIE_PROTECTION = True
# Default setting is internal
# External Supported Sources: ldap, kerberos, oauth2
# Multiple authentication can be achieved by setting this parameter to
-# ['ldap', 'internal'] or ['oauth2', 'internal'] etc.
+# ['ldap', 'internal'] or ['oauth2', 'internal'] or
+# ['webserver', 'internal'] etc.
# pgAdmin will authenticate the user with ldap/oauth2 whatever first in the
# list, in case of failure the second authentication option will be considered.
@@ -729,6 +730,12 @@ OAUTH2_CONFIG = [
OAUTH2_AUTO_CREATE_USER = True
+##########################################################################
+# Webserver Configuration
+##########################################################################
+
+WEBSERVER_AUTO_CREATE_USER = True
+
##########################################################################
# PSQL tool settings
##########################################################################
diff --git a/web/pgAdmin4.wsgi b/web/pgAdmin4.wsgi
index 973d2701d..95aab8de2 100644
--- a/web/pgAdmin4.wsgi
+++ b/web/pgAdmin4.wsgi
@@ -13,6 +13,8 @@ import sys
if sys.version_info < (3, 4):
raise Exception('This application must be run under Python 3.4 or later.')
+os.environ['SCRIPT_NAME'] = '/pgadmin4'
+
import builtins
root = os.path.dirname(os.path.realpath(__file__))
diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py
index 37eb26ccc..8047b3417 100644
--- a/web/pgadmin/__init__.py
+++ b/web/pgadmin/__init__.py
@@ -46,7 +46,7 @@ from pgadmin.utils.ajax import internal_server_error, make_json_response
from pgadmin.utils.csrf import pgCSRFProtect
from pgadmin import authenticate
from pgadmin.utils.security_headers import SecurityHeaders
-from pgadmin.utils.constants import KERBEROS, OAUTH2, INTERNAL, LDAP
+from pgadmin.utils.constants import KERBEROS, OAUTH2, INTERNAL, LDAP, WEBSERVER
# 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
@@ -470,11 +470,17 @@ def create_app(app_name=None):
'SECURITY_EMAIL_VALIDATOR_ARGS': config.SECURITY_EMAIL_VALIDATOR_ARGS
}))
+ if 'SCRIPT_NAME' in os.environ and os.environ["SCRIPT_NAME"]:
+ app.config.update(dict({
+ 'APPLICATION_ROOT': os.environ["SCRIPT_NAME"]
+ }))
+
app.config.update(dict({
'INTERNAL': INTERNAL,
'LDAP': LDAP,
'KERBEROS': KERBEROS,
- 'OAUTH2': OAUTH2
+ 'OAUTH2': OAUTH2,
+ 'WEBSERVER': WEBSERVER
}))
security.init_app(app, user_datastore)
@@ -771,15 +777,14 @@ def create_app(app_name=None):
elif config.SERVER_MODE and \
not current_user.is_authenticated and \
request.endpoint in ('redirects.index', 'security.login'):
- if app.PGADMIN_EXTERNAL_AUTH_SOURCE == KERBEROS:
+ if app.PGADMIN_EXTERNAL_AUTH_SOURCE in [KERBEROS, WEBSERVER]:
return authenticate.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 app.PGADMIN_EXTERNAL_AUTH_SOURCE != \
- OAUTH2 and\
+ app.PGADMIN_EXTERNAL_AUTH_SOURCE not in [
+ KERBEROS, OAUTH2, WEBSERVER] and \
current_app.keyManager.get() is None and \
request.endpoint not in ('security.login', 'security.logout'):
logout_user()
diff --git a/web/pgadmin/authenticate/webserver.py b/web/pgadmin/authenticate/webserver.py
new file mode 100644
index 000000000..47af8becd
--- /dev/null
+++ b/web/pgadmin/authenticate/webserver.py
@@ -0,0 +1,119 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""A blueprint module implementing the Webserver authentication."""
+
+import random
+import string
+import config
+from flask import request, current_app, session, Response, render_template, \
+ url_for
+from flask_babelex import gettext
+from flask_security import login_user
+from .internal import BaseAuthentication
+from pgadmin.model import User
+from pgadmin.tools.user_management import create_user
+from pgadmin.utils.constants import WEBSERVER
+from pgadmin.utils import PgAdminModule
+from pgadmin.utils.csrf import pgCSRFProtect
+from flask_security.utils import logout_user
+from os import environ, path, remove
+
+
+class WebserverModule(PgAdminModule):
+ def register(self, app, options, first_registration=False):
+ # Do not look for the sub_modules,
+ # instead call blueprint.register(...) directly
+ super(PgAdminModule, self).register(app, options, first_registration)
+
+ def get_exposed_url_endpoints(self):
+ return ['webserver.login',
+ 'webserver.logout']
+
+
+def init_app(app):
+ MODULE_NAME = 'webserver'
+
+ blueprint = WebserverModule(MODULE_NAME, __name__, static_url_path='')
+
+ @blueprint.route("/login",
+ endpoint="login", methods=["GET"])
+ @pgCSRFProtect.exempt
+ def webserver_login():
+ logout_user()
+ return Response(render_template("browser/kerberos_login.html",
+ login_url=url_for('security.login'),
+ ))
+
+ @blueprint.route("/logout",
+ endpoint="logout", methods=["GET"])
+ @pgCSRFProtect.exempt
+ def webserver_logout():
+ logout_user()
+ return Response(render_template("browser/kerberos_logout.html",
+ login_url=url_for('security.login'),
+ ))
+
+ app.register_blueprint(blueprint)
+
+
+class WebserverAuthentication(BaseAuthentication):
+ LOGIN_VIEW = 'webserver.login'
+ LOGOUT_VIEW = 'webserver.logout'
+
+ def get_source_name(self):
+ return WEBSERVER
+
+ def get_friendly_name(self):
+ return gettext("webserver")
+
+ def validate(self, form):
+ return True
+
+ def get_user(self):
+ return request.environ.get('REMOTE_USER')
+
+ def authenticate(self, form):
+ username = self.get_user()
+
+ if not username:
+ return False, gettext(
+ "Webserver authenticate failed.")
+
+ session['pass_enc_key'] = ''.join(
+ (random.choice(string.ascii_lowercase) for x in range(10)))
+ useremail = request.environ.get('mail')
+ if not useremail:
+ useremail = ''
+ return self.__auto_create_user(username, '')
+
+ def login(self, form):
+ username = self.get_user()
+ if username:
+ user = User.query.filter_by(username=username).first()
+ status = login_user(user)
+ if not status:
+ current_app.logger.exception(self.messages('LOGIN_FAILED'))
+ return False, self.messages('LOGIN_FAILED')
+ return True, None
+ return False, self.messages('LOGIN_FAILED')
+
+ def __auto_create_user(self, username, useremail):
+ """Add the webserver user to the internal SQLite database."""
+ if config.WEBSERVER_AUTO_CREATE_USER:
+ user = User.query.filter_by(username=username).first()
+ if not user:
+ return create_user({
+ 'username': username,
+ 'email': useremail,
+ 'role': 2,
+ 'active': True,
+ 'auth_source': WEBSERVER
+ })
+ return True, None
diff --git a/web/pgadmin/browser/tests/test_kerberos_with_mocking.py b/web/pgadmin/browser/tests/test_kerberos_with_mocking.py
index 0f49c444d..982e7bcde 100644
--- a/web/pgadmin/browser/tests/test_kerberos_with_mocking.py
+++ b/web/pgadmin/browser/tests/test_kerberos_with_mocking.py
@@ -133,4 +133,5 @@ class KerberosLoginMockTestCase(BaseTestGenerator):
"""
cls.tester.logout()
app_config.AUTHENTICATION_SOURCES = [INTERNAL]
+ app_config.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/browser/tests/test_ldap_login.py b/web/pgadmin/browser/tests/test_ldap_login.py
index 51b512afd..ec2c7d025 100644
--- a/web/pgadmin/browser/tests/test_ldap_login.py
+++ b/web/pgadmin/browser/tests/test_ldap_login.py
@@ -95,4 +95,5 @@ class LDAPLoginTestCase(BaseTestGenerator):
"""
cls.tester.logout()
app_config.AUTHENTICATION_SOURCES = [INTERNAL]
+ app_config.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/browser/tests/test_ldap_with_mocking.py b/web/pgadmin/browser/tests/test_ldap_with_mocking.py
index 38c6b4724..19b548cd4 100644
--- a/web/pgadmin/browser/tests/test_ldap_with_mocking.py
+++ b/web/pgadmin/browser/tests/test_ldap_with_mocking.py
@@ -81,4 +81,5 @@ class LDAPLoginMockTestCase(BaseTestGenerator):
"""
cls.tester.logout()
app_config.AUTHENTICATION_SOURCES = [INTERNAL]
+ app_config.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/browser/tests/test_login.py b/web/pgadmin/browser/tests/test_login.py
index 451c05b64..f5ce10927 100644
--- a/web/pgadmin/browser/tests/test_login.py
+++ b/web/pgadmin/browser/tests/test_login.py
@@ -100,6 +100,7 @@ class LoginTestCase(BaseTestGenerator):
# No need to call base class setup function
def setUp(self):
app_config.AUTHENTICATION_SOURCES = [INTERNAL]
+ app_config.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
def runTest(self):
"""This function checks login functionality."""
diff --git a/web/pgadmin/browser/tests/test_oauth2_with_mocking.py b/web/pgadmin/browser/tests/test_oauth2_with_mocking.py
index 71706ebe6..0ab6f7cdd 100644
--- a/web/pgadmin/browser/tests/test_oauth2_with_mocking.py
+++ b/web/pgadmin/browser/tests/test_oauth2_with_mocking.py
@@ -145,4 +145,5 @@ class Oauth2LoginMockTestCase(BaseTestGenerator):
"""
cls.tester.logout()
app_config.AUTHENTICATION_SOURCES = [INTERNAL]
+ app_config.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/browser/tests/test_webserver_with_mocking.py b/web/pgadmin/browser/tests/test_webserver_with_mocking.py
new file mode 100644
index 000000000..74343d135
--- /dev/null
+++ b/web/pgadmin/browser/tests/test_webserver_with_mocking.py
@@ -0,0 +1,86 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2021, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import config as app_config
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from pgadmin.authenticate.registry import AuthSourceRegistry
+from unittest.mock import patch, MagicMock
+from pgadmin.authenticate import AuthSourceManager
+from pgadmin.utils.constants import OAUTH2, LDAP, INTERNAL, WEBSERVER
+from flask import request
+
+
+class WebserverLoginMockTestCase(BaseTestGenerator):
+ """
+ This class checks oauth2 login functionality by mocking
+ Webserver Authentication.
+ """
+
+ scenarios = [
+ ('Webserver Authentication', dict(
+ auth_source=[WEBSERVER],
+ username='test_mock_webserver_user'
+ )),
+ ]
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ We need to logout the test client as we are testing
+ spnego/kerberos login scenarios.
+ """
+ cls.tester.logout()
+
+ def setUp(self):
+ app_config.AUTHENTICATION_SOURCES = self.auth_source
+ self.app.PGADMIN_EXTERNAL_AUTH_SOURCE = WEBSERVER
+
+ def runTest(self):
+ """This function checks webserver login functionality."""
+ if app_config.SERVER_MODE is False:
+ self.skipTest(
+ "Can not run Webserver Authentication in the Desktop mode."
+ )
+
+ self.test_webserver_authentication()
+
+ def test_webserver_authentication(self):
+ """
+ Ensure that when the client sends an correct authorization token,
+ they receive a 200 OK response and the user principal is extracted and
+ passed on to the routed method.
+ """
+
+ # Mock Oauth2 Authenticate
+ AuthSourceRegistry._registry[WEBSERVER].get_user = MagicMock(
+ return_value=self.username)
+
+ res = self.tester.login(None,
+ None,
+ True,
+ None
+ )
+ self.assertEqual(res.status_code, 200)
+ respdata = 'Gravatar image for %s' % self.username
+ self.assertTrue(respdata in res.data.decode('utf8'))
+
+ def tearDown(self):
+ pass
+
+ @classmethod
+ def tearDownClass(cls):
+ """
+ We need to again login the test client as soon as test scenarios
+ finishes.
+ """
+ cls.tester.logout()
+ app_config.AUTHENTICATION_SOURCES = [INTERNAL]
+ app_config.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
+ utils.login_tester_account(cls.tester)
diff --git a/web/pgadmin/utils/constants.py b/web/pgadmin/utils/constants.py
index 91931c422..96d46367b 100644
--- a/web/pgadmin/utils/constants.py
+++ b/web/pgadmin/utils/constants.py
@@ -55,12 +55,14 @@ ERROR_FETCHING_DATA = gettext('Unable to fetch data.')
INTERNAL = 'internal'
LDAP = 'ldap'
KERBEROS = 'kerberos'
-OAUTH2 = "oauth2"
+OAUTH2 = 'oauth2'
+WEBSERVER = 'webserver'
SUPPORTED_AUTH_SOURCES = [INTERNAL,
LDAP,
KERBEROS,
- OAUTH2]
+ OAUTH2,
+ WEBSERVER]
BINARY_PATHS = {
"as_bin_paths": [
view thread (3+ 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: [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication.
In-Reply-To: <CAFOhELdQXmy3v3wnQYuxNDrz2+q48jzgr9RgSXt4gQ0LY81fBA@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox