public inbox for [email protected]  
help / color / mirror / Atom feed
[pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication.
3+ messages / 3 participants
[nested] [flat]

* [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication.
@ 2021-10-12 05:13 Khushboo Vashi <[email protected]>
  2021-10-12 08:10 ` Re: [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication. Dave Page <[email protected]>
  2021-10-12 09:35 ` Re: [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication. Akshay Joshi <[email protected]>
  0 siblings, 2 replies; 3+ messages in thread

From: Khushboo Vashi @ 2021-10-12 05:13 UTC (permalink / raw)
  To: pgadmin-hackers

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": [


^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication.
  2021-10-12 05:13 [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication. Khushboo Vashi <[email protected]>
@ 2021-10-12 08:10 ` Dave Page <[email protected]>
  1 sibling, 0 replies; 3+ messages in thread

From: Dave Page @ 2021-10-12 08:10 UTC (permalink / raw)
  To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers

Very nice (the existence of the patch that is - I haven't reviewed it)! I
think this nicely rounds off and completes the auth mechanisms we're likely
to want.

On Tue, Oct 12, 2021 at 6:13 AM Khushboo Vashi <
[email protected]> wrote:

> 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
>


-- 
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake

EDB: https://www.enterprisedb.com


^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication.
  2021-10-12 05:13 [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication. Khushboo Vashi <[email protected]>
@ 2021-10-12 09:35 ` Akshay Joshi <[email protected]>
  1 sibling, 0 replies; 3+ messages in thread

From: Akshay Joshi @ 2021-10-12 09:35 UTC (permalink / raw)
  To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers

Thanks, the patch applied.

On Tue, Oct 12, 2021 at 10:43 AM Khushboo Vashi <
[email protected]> wrote:

> 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
>


-- 
*Thanks & Regards*
*Akshay Joshi*
*pgAdmin Hacker | Principal Software Architect*
*EDB Postgres <http://edbpostgres.com>*

*Mobile: +91 976-788-8246*


^ permalink  raw  reply  [nested|flat] 3+ messages in thread


end of thread, other threads:[~2021-10-12 09:35 UTC | newest]

Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-10-12 05:13 [pgAdmin4][Patch] - RM 6657 - Support for REMOTE_USER as Authentication. Khushboo Vashi <[email protected]>
2021-10-12 08:10 ` Dave Page <[email protected]>
2021-10-12 09:35 ` Akshay Joshi <[email protected]>

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