1) Added support for Kerberos authentication, using SPNEGO to forward the Kerberos tickets through a browser. Fixes #5457

2) Fixed incorrect log information for AUTHENTICATION_SOURCES. Fixes #5829
This commit is contained in:
Khushboo Vashi 2021-01-14 13:46:48 +05:30 committed by Akshay Joshi
parent 9a47e574e3
commit 6ead597b43
22 changed files with 474 additions and 40 deletions

View File

@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #5457 <https://redmine.postgresql.org/issues/5457>`_ - Added support for Kerberos authentication, using SPNEGO to forward the Kerberos tickets through a browser.
Housekeeping
************
@ -23,6 +24,7 @@ Bug fixes
| `Issue #5282 <https://redmine.postgresql.org/issues/5282>`_ - Added 'Count Rows' option to the partition sub tables.
| `Issue #5488 <https://redmine.postgresql.org/issues/5488>`_ - Improve the explain plan details by showing popup instead of tooltip on clicking of the specified node.
| `Issue #5571 <https://redmine.postgresql.org/issues/5571>`_ - Added support for expression in exclusion constraints.
| `Issue #5829 <https://redmine.postgresql.org/issues/5829>`_ - Fixed incorrect log information for AUTHENTICATION_SOURCES.
| `Issue #5875 <https://redmine.postgresql.org/issues/5875>`_ - Ensure that the 'template1' database should not be visible after pg_upgrade.
| `Issue #5965 <https://redmine.postgresql.org/issues/5965>`_ - Ensure that the macro query result should be download properly.
| `Issue #5973 <https://redmine.postgresql.org/issues/5973>`_ - Added appropriate help message and a placeholder for letting users know about the account password expiry for Login/Group Role.

View File

@ -43,3 +43,4 @@ cryptography<=3.0;
sshtunnel>=0.1.5
ldap3>=2.5.1
Flask-BabelEx>=0.9.4
gssapi>=1.6.11

View File

@ -535,7 +535,7 @@ ENHANCED_COOKIE_PROTECTION = True
##########################################################################
# Default setting is internal
# External Supported Sources: ldap
# External Supported Sources: ldap, kerberos
# Multiple authentication can be achieved by setting this parameter to
# ['ldap', 'internal']. pgAdmin will authenticate the user with ldap first,
# in case of failure internal authentication will be done.
@ -618,6 +618,26 @@ LDAP_CA_CERT_FILE = ''
LDAP_CERT_FILE = ''
LDAP_KEY_FILE = ''
##########################################################################
# Kerberos Configuration
##########################################################################
KRB_APP_HOST_NAME = DEFAULT_SERVER
# If the default_keytab_name is not set in krb5.conf or
# the KRB_KTNAME environment variable is not set then, explicitly set
# the Keytab file
KRB_KTNAME = '<KRB5_KEYTAB_FILE>'
# After kerberos authentication, user will be added into the SQLite database
# automatically, if set to True.
# Set it to False, if user should not be added automatically,
# in this case Admin has to add the user manually in the SQLite database.
KRB_AUTO_CREATE_USER = True
##########################################################################
# Local config settings
##########################################################################

View File

@ -35,6 +35,9 @@ else:
import config
from pgadmin import create_app
from pgadmin.utils import u_encode, fs_encoding, file_quote
from pgadmin.utils.constants import INTERNAL, LDAP,\
KERBEROS, SUPPORTED_AUTH_SOURCES
# Get the config database schema version. We store this in pgadmin.model
# as it turns out that putting it in the config files isn't a great idea
from pgadmin.model import SCHEMA_VERSION
@ -96,15 +99,11 @@ if config.SERVER_MODE:
app.wsgi_app = ReverseProxied(app.wsgi_app)
# Authentication sources
app.PGADMIN_DEFAULT_AUTH_SOURCE = 'internal'
app.PGADMIN_SUPPORTED_AUTH_SOURCE = ['internal', 'ldap']
if len(config.AUTHENTICATION_SOURCES) > 0:
app.PGADMIN_EXTERNAL_AUTH_SOURCE = config.AUTHENTICATION_SOURCES[0]
else:
app.PGADMIN_EXTERNAL_AUTH_SOURCE = app.PGADMIN_DEFAULT_AUTH_SOURCE
app.logger.debug(
"Authentication Source: %s" % app.PGADMIN_DEFAULT_AUTH_SOURCE)
app.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
# Start the web server. The port number should have already been set by the
# runtime if we're running in desktop mode, otherwise we'll just use the

View File

@ -43,6 +43,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
# 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
@ -695,11 +696,19 @@ def create_app(app_name=None):
)
abort(401)
login_user(user)
elif config.SERVER_MODE and\
app.PGADMIN_EXTERNAL_AUTH_SOURCE ==\
KERBEROS and \
not current_user.is_authenticated and \
request.endpoint in ('redirects.index', 'security.login'):
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 \
current_app.keyManager.get() is None and \
request.endpoint not in ('security.login', 'security.logout'):
logout_user()

View File

@ -11,16 +11,21 @@
import flask
import pickle
from flask import current_app, flash
from flask import current_app, flash, Response, request, url_for,\
render_template
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, \
get_post_login_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
MODULE_NAME = 'authenticate'
@ -28,12 +33,34 @@ MODULE_NAME = 'authenticate'
class AuthenticateModule(PgAdminModule):
def get_exposed_url_endpoints(self):
return ['authenticate.login']
return ['authenticate.login',
'authenticate.kerberos_login',
'authenticate.kerberos_logout']
blueprint = AuthenticateModule(MODULE_NAME, __name__, static_url_path='')
@blueprint.route("/login/kerberos",
endpoint="kerberos_login", methods=["GET"])
@pgCSRFProtect.exempt
def kerberos_login():
logout_user()
return Response(render_template("browser/kerberos_login.html",
login_url=url_for('security.login'),
))
@blueprint.route("/logout/kerberos",
endpoint="kerberos_logout", methods=["GET"])
@pgCSRFProtect.exempt
def kerberos_logout():
logout_user()
return Response(render_template("browser/kerberos_logout.html",
login_url=url_for('security.login'),
))
@blueprint.route('/login', endpoint='login', methods=['GET', 'POST'])
def login():
"""
@ -56,15 +83,24 @@ def login():
if status:
# Login the user
status, msg = auth_obj.login()
current_auth_obj = auth_obj.as_dict()
if not status:
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'] = auth_obj.as_dict()
session['_auth_source_manager_obj'] = current_auth_obj
return flask.redirect(get_post_login_redirect())
elif isinstance(msg, Response):
return msg
flash(gettext(msg), 'danger')
return flask.redirect(get_post_logout_redirect())
response = flask.redirect(get_post_logout_redirect())
return response
class AuthSourceManager():
@ -75,6 +111,7 @@ class AuthSourceManager():
self.auth_sources = sources
self.source = None
self.source_friendly_name = None
self.current_source = None
def as_dict(self):
"""
@ -84,9 +121,17 @@ class AuthSourceManager():
res = dict()
res['source_friendly_name'] = self.source_friendly_name
res['auth_sources'] = self.auth_sources
res['current_source'] = self.current_source
return res
def set_current_source(self, source):
self.current_source = source
@property
def get_current_source(self, source):
return self.current_source
def set_source(self, source):
self.source = source
@ -115,9 +160,33 @@ class AuthSourceManager():
msg = None
for src in self.auth_sources:
source = get_auth_sources(src)
current_app.logger.debug(
"Authentication initiated via source: %s" %
source.get_source_name())
if self.form.data['email'] and self.form.data['password'] and \
source.get_source_name() == KERBEROS:
continue
status, msg = source.authenticate(self.form)
# When server sends Unauthorized header to get the ticket over HTTP
# OR When kerberos authentication failed while accessing pgadmin,
# we need to break the loop as no need to authenticate further
# even if the authentication sources set to multiple
if not status:
if (hasattr(msg, 'status') and
msg.status == '401 UNAUTHORIZED') or\
(source.get_source_name() ==
KERBEROS and
request.method == 'GET'):
break
if status:
self.set_source(source)
self.set_current_source(source.get_source_name())
if msg is not None and 'username' in msg:
self.form._fields['email'].data = msg['username']
return status, msg
return status, msg
@ -125,6 +194,9 @@ class AuthSourceManager():
status, msg = self.source.login(self.form)
if status:
self.set_source_friendly_name(self.source.get_friendly_name())
current_app.logger.debug(
"Authentication and Login successfully done via source : %s" %
self.source.get_source_name())
return status, msg

View File

@ -18,6 +18,7 @@ from flask_babelex import gettext
from .registry import AuthSourceRegistry
from pgadmin.model import User
from pgadmin.utils.validation_utils import validate_email
from pgadmin.utils.constants import INTERNAL
@six.add_metaclass(AuthSourceRegistry)
@ -31,7 +32,11 @@ class BaseAuthentication(object):
'INVALID_EMAIL': gettext('Email/Username is not valid')
}
@abstractproperty
@abstractmethod
def get_source_name(self):
pass
@abstractmethod
def get_friendly_name(self):
pass
@ -82,6 +87,9 @@ class BaseAuthentication(object):
class InternalAuthentication(BaseAuthentication):
def get_source_name(self):
return INTERNAL
def get_friendly_name(self):
return gettext("internal")

View File

@ -0,0 +1,138 @@
##########################################################################
#
# 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 Spnego/Kerberos authentication."""
import base64
import gssapi
from os import environ
from werkzeug.datastructures import Headers
from flask_babelex import gettext
from flask import Flask, request, Response, session,\
current_app, render_template, flash
import config
from pgadmin.model import User, ServerGroup, db, Role
from pgadmin.tools.user_management import create_user
from pgadmin.utils.constants import KERBEROS
from flask_security.views import _security, _commit, _ctx
from werkzeug.datastructures import MultiDict
from .internal import BaseAuthentication
# Set the Kerberos config file
if config.KRB_KTNAME and config.KRB_KTNAME != '<KRB5_KEYTAB_FILE>':
environ['KRB5_KTNAME'] = config.KRB_KTNAME
class KerberosAuthentication(BaseAuthentication):
def get_source_name(self):
return KERBEROS
def get_friendly_name(self):
return gettext("kerberos")
def validate(self, form):
return True
def authenticate(self, frm):
retval = [True, None]
negotiate = False
headers = Headers()
authorization = request.headers.get("Authorization", None)
form_class = _security.login_form
if request.json:
form = form_class(MultiDict(request.json))
else:
form = form_class()
try:
if authorization is not None:
auth_header = authorization.split()
if auth_header[0] == 'Negotiate':
status, negotiate = self.negotiate_start(auth_header[1])
if status:
# Saving the first 15 characters of the kerberos key
# to encrypt/decrypt database password
session['kerberos_key'] = auth_header[1][0:15]
# Create user
retval = self.__auto_create_user(
str(negotiate.initiator_name))
elif isinstance(negotiate, Exception):
flash(gettext(negotiate), 'danger')
retval = [status,
Response(render_template(
"security/login_user.html",
login_user_form=form))]
else:
headers.add('WWW-Authenticate', 'Negotiate ' +
str(base64.b64encode(negotiate), 'utf-8'))
return False, Response("Success", 200, headers)
else:
flash(gettext("Kerberos authentication failed."
" Couldn't find kerberos ticket."), 'danger')
headers.add('WWW-Authenticate', 'Negotiate')
retval = [False,
Response(render_template(
"security/login_user.html",
login_user_form=form), 401, headers)]
finally:
if negotiate is not False:
self.negotiate_end(negotiate)
return retval
def negotiate_start(self, in_token):
svc_princ = gssapi.Name('HTTP@%s' % config.KRB_APP_HOST_NAME,
name_type=gssapi.NameType.hostbased_service)
cname = svc_princ.canonicalize(gssapi.MechType.kerberos)
try:
server_creds = gssapi.Credentials(usage='accept', name=cname)
context = gssapi.SecurityContext(creds=server_creds)
out_token = context.step(base64.b64decode(in_token))
except Exception as e:
current_app.logger.exception(e)
return False, e
if out_token and not context.complete:
return False, out_token
if context.complete:
return True, context
else:
return False, None
def negotiate_end(self, context):
# Free gss_cred_id_t
del_creds = getattr(context, 'delegated_creds', None)
if del_creds:
deleg_creds = context.delegated_creds
del(deleg_creds)
def __auto_create_user(self, username):
"""Add the ldap user to the internal SQLite database."""
username = str(username)
if config.KRB_AUTO_CREATE_USER:
user = User.query.filter_by(
username=username).first()
if user is None:
return create_user({
'username': username,
'email': username,
'role': 2,
'active': True,
'auth_source': KERBEROS
})
return True, {'username': username}

View File

@ -23,6 +23,7 @@ from .internal import BaseAuthentication
from pgadmin.model import User, ServerGroup, db, Role
from flask import current_app
from pgadmin.tools.user_management import create_user
from pgadmin.utils.constants import LDAP
ERROR_SEARCHING_LDAP_DIRECTORY = "Error searching the LDAP directory: {}"
@ -31,6 +32,9 @@ ERROR_SEARCHING_LDAP_DIRECTORY = "Error searching the LDAP directory: {}"
class LDAPAuthentication(BaseAuthentication):
"""Ldap Authentication Class"""
def get_source_name(self):
return LDAP
def get_friendly_name(self):
return gettext("ldap")
@ -151,7 +155,7 @@ class LDAPAuthentication(BaseAuthentication):
'email': user_email,
'role': 2,
'active': True,
'auth_source': 'ldap'
'auth_source': LDAP
})
return True, None

View File

@ -29,7 +29,7 @@ from flask_security.recoverable import reset_password_token_status, \
generate_reset_password_token, update_password
from flask_security.signals import reset_password_instructions_sent
from flask_security.utils import config_value, do_flash, get_url, \
get_message, slash_url_suffix, login_user, send_mail
get_message, slash_url_suffix, login_user, send_mail, logout_user
from flask_security.views import _security, _commit, _ctx
from werkzeug.datastructures import MultiDict
@ -47,7 +47,8 @@ from pgadmin.utils.master_password import validate_master_password, \
set_masterpass_check_text, cleanup_master_password, get_crypt_key, \
set_crypt_key, process_masterpass_disabled
from pgadmin.model import User
from pgadmin.utils.constants import MIMETYPE_APP_JS, PGADMIN_NODE
from pgadmin.utils.constants import MIMETYPE_APP_JS, PGADMIN_NODE,\
INTERNAL, KERBEROS
try:
from flask_security.views import default_render_json
@ -280,7 +281,8 @@ class BrowserModule(PgAdminModule):
'browser.check_master_password',
'browser.set_master_password',
'browser.reset_master_password',
'browser.lock_layout']
'browser.lock_layout'
]
blueprint = BrowserModule(MODULE_NAME, __name__)
@ -539,6 +541,12 @@ 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))
return '{0}?next={1}'.format(
url_for('security.logout'), url_for(BROWSER_INDEX))
@ -664,13 +672,18 @@ def index():
auth_only_internal = False
auth_source = []
session['allow_save_password'] = True
if config.SERVER_MODE:
if len(config.AUTHENTICATION_SOURCES) == 1\
and 'internal' in config.AUTHENTICATION_SOURCES:
and INTERNAL in config.AUTHENTICATION_SOURCES:
auth_only_internal = True
auth_source = session['_auth_source_manager_obj'][
'source_friendly_name']
if session['_auth_source_manager_obj']['current_source'] == KERBEROS:
session['allow_save_password'] = False
response = Response(render_template(
MODULE_NAME + "/index.html",
username=current_user.username,
@ -1086,7 +1099,7 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
# Check the Authentication source of the User
user = User.query.filter_by(
email=form.data['email'],
auth_source=current_app.PGADMIN_DEFAULT_AUTH_SOURCE
auth_source=INTERNAL
).first()
if user is None:

View File

@ -10,7 +10,7 @@
import simplejson as json
import pgadmin.browser.server_groups as sg
from flask import render_template, request, make_response, jsonify, \
current_app, url_for
current_app, url_for, session
from flask_babelex import gettext
from flask_security import current_user, login_required
from pgadmin.browser.server_groups.servers.types import ServerType
@ -1822,7 +1822,13 @@ class ServerNode(PGChildNodeView):
_=gettext,
service=server.service,
prompt_tunnel_password=prompt_tunnel_password,
prompt_password=prompt_password
prompt_password=prompt_password,
allow_save_password=True if
config.ALLOW_SAVE_PASSWORD and
session['allow_save_password'] else False,
allow_save_tunnel_password=True if
config.ALLOW_SAVE_TUNNEL_PASSWORD and
session['allow_save_password'] else False
)
)
else:
@ -1836,6 +1842,9 @@ class ServerNode(PGChildNodeView):
errmsg=errmsg,
service=server.service,
_=gettext,
allow_save_password=True if
config.ALLOW_SAVE_PASSWORD and
session['allow_save_password'] else False,
)
)

View File

@ -19,7 +19,7 @@
<div class="col-sm-10">
<div class="custom-control custom-checkbox">
<input class="custom-control-input" id="save_password" name="save_password" type="checkbox"
{% if not config.ALLOW_SAVE_PASSWORD %}disabled{% endif %}
{% if not allow_save_password %}disabled{% endif %}
>
<label class="custom-control-label" for="save_password">{{ _('Save Password') }}</label>
</div>

View File

@ -15,7 +15,7 @@
<div class="w-100">
<div class="custom-control custom-checkbox">
<input class="custom-control-input" id="save_tunnel_password" name="save_tunnel_password" type="checkbox"
{% if not config.ALLOW_SAVE_TUNNEL_PASSWORD %}disabled{% endif %}
{% if not allow_save_tunnel_password %}disabled{% endif %}
>
<label class="custom-control-label" for="save_tunnel_password" class="ml-1">{{ _('Save Password') }}</label>
</div>
@ -39,7 +39,7 @@
<div class="w-100">
<div class="custom-control custom-checkbox">
<input class="custom-control-input" id="save_password" name="save_password" type="checkbox"
{% if not config.ALLOW_SAVE_PASSWORD %}disabled{% endif %}
{% if not allow_save_password %}disabled{% endif %}
>
<label class="custom-control-label" for="save_password" class="ml-1">{{ _('Save Password') }}</label>
</div>

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block body %}
<div class="container-fluid change_pass">
<div class="row align-items-center h-100">
<div class="col-md-5"></div>
<div class="col-md-5">
<div class="panel-header h4"><i class="app-icon pg-icon-blue" aria-hidden="true"></i> {{ _('%(appname)s', appname=config.APP_NAME) }}</div>
<div class="panel-body">
<div class="d-block text-color pb-3 h5">{{ _('Login Failed.') }}</div>
<div><a href="{{ login_url }}">Click here</a> to Login again.</div>
</div>
</div>
<div class="col-md-4"></div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block body %}
<div class="container-fluid change_pass">
<div class="row align-items-center h-100">
<div class="col-md-5"></div>
<div class="col-md-5">
<div class="panel-header h4"><i class="app-icon pg-icon-blue" aria-hidden="true"></i> {{ _('%(appname)s', appname=config.APP_NAME) }}</div>
<div class="panel-body">
<div class="d-block text-color pb-3 h5">{{ _('Logged out successfully.') }}</div>
<div><a href="{{ login_url }}">Click here</a> to Login again.</div>
</div>
</div>
<div class="col-md-4"></div>
</div>
</div>
{% endblock %}

View File

@ -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 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
class KerberosLoginMockTestCase(BaseTestGenerator):
"""
This class checks Spnego/Kerberos login functionality by mocking
HTTP negotiate authentication.
"""
scenarios = [
('Spnego/Kerberos Authentication: Test Unauthorized', dict(
auth_source=['kerberos'],
auto_create_user=True,
flag=1
)),
('Spnego/Kerberos Authentication: Test Authorized', dict(
auth_source=['kerberos'],
auto_create_user=True,
flag=2
))
]
@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 = 'kerberos'
def runTest(self):
"""This function checks spnego/kerberos login functionality."""
if self.flag == 1:
self.test_unauthorized()
elif self.flag == 2:
if app_config.SERVER_MODE is False:
self.skipTest(
"Can not run Kerberos Authentication in the Desktop mode."
)
self.test_authorized()
def test_unauthorized(self):
"""
Ensure that when client sends the first request,
the Negotiate request is sent.
"""
res = self.tester.login(None, None, True)
self.assertEqual(res.status_code, 401)
self.assertEqual(res.headers.get('www-authenticate'), 'Negotiate')
def test_authorized(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.
"""
class delCrads:
def __init__(self):
self.initiator_name = 'user@PGADMIN.ORG'
del_crads = delCrads()
AuthSourceRegistry.registry['kerberos'].negotiate_start = MagicMock(
return_value=[True, del_crads])
res = self.tester.login(None,
None,
True,
headers={'Authorization': 'Negotiate CTOKEN'}
)
self.assertEqual(res.status_code, 200)
respdata = 'Gravatar image for %s' % del_crads.initiator_name
self.assertTrue(respdata in res.data.decode('utf8'))
def tearDown(self):
self.app.PGADMIN_EXTERNAL_AUTH_SOURCE = 'ldap'
self.tester.logout()
@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']
utils.login_tester_account(cls.tester)

View File

@ -25,7 +25,7 @@ from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_json_response, bad_request, \
internal_server_error, unauthorized
from config import PG_DEFAULT_DRIVER
from config import PG_DEFAULT_DRIVER, ALLOW_SAVE_PASSWORD
from pgadmin.model import Server, User
from pgadmin.utils.driver import get_driver
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
@ -402,6 +402,9 @@ def _init_query_tool(trans_id, connect, sgid, sid, did, **kwargs):
username=user,
errmsg=msg,
_=gettext,
allow_save_password=True if
ALLOW_SAVE_PASSWORD and
session['allow_save_password'] else False,
)
), '', ''
else:

View File

@ -13,7 +13,7 @@ import simplejson as json
import re
from flask import render_template, request, \
url_for, Response, abort, current_app
url_for, Response, abort, current_app, session
from flask_babelex import gettext as _
from flask_security import login_required, roles_required, current_user
from flask_security.utils import encrypt_password
@ -24,7 +24,8 @@ from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_response as ajax_response, \
make_json_response, bad_request, internal_server_error, forbidden
from pgadmin.utils.csrf import pgCSRFProtect
from pgadmin.utils.constants import MIMETYPE_APP_JS
from pgadmin.utils.constants import MIMETYPE_APP_JS, INTERNAL,\
SUPPORTED_AUTH_SOURCES, KERBEROS
from pgadmin.utils.validation_utils import validate_email
from pgadmin.model import db, Role, User, UserPreference, Server, \
ServerGroup, Process, Setting
@ -167,11 +168,13 @@ def current_user_info():
config.SERVER_MODE is True
else 'postgres'
),
allow_save_password='true' if config.ALLOW_SAVE_PASSWORD
allow_save_password='true' if
config.ALLOW_SAVE_PASSWORD and session['allow_save_password']
else 'false',
allow_save_tunnel_password='true'
if config.ALLOW_SAVE_TUNNEL_PASSWORD else 'false',
auth_sources=config.AUTHENTICATION_SOURCES,
allow_save_tunnel_password='true' if
config.ALLOW_SAVE_TUNNEL_PASSWORD and session[
'allow_save_password'] else 'false',
auth_sources=config.AUTHENTICATION_SOURCES
),
status=200,
mimetype=MIMETYPE_APP_JS
@ -254,10 +257,10 @@ def _create_new_user(new_data):
:return: Return new created user.
"""
auth_source = new_data['auth_source'] if 'auth_source' in new_data \
else current_app.PGADMIN_DEFAULT_AUTH_SOURCE
else INTERNAL
username = new_data['username'] if \
'username' in new_data and auth_source != \
current_app.PGADMIN_DEFAULT_AUTH_SOURCE else new_data['email']
INTERNAL else new_data['email']
email = new_data['email'] if 'email' in new_data else None
password = new_data['password'] if 'password' in new_data else None
@ -279,7 +282,7 @@ def _create_new_user(new_data):
def create_user(data):
if 'auth_source' in data and data['auth_source'] != \
current_app.PGADMIN_DEFAULT_AUTH_SOURCE:
INTERNAL:
req_params = ('username', 'role', 'active', 'auth_source')
else:
req_params = ('email', 'role', 'active', 'newPassword',
@ -380,7 +383,7 @@ def update(uid):
)
# Username and email can not be changed for internal users
if usr.auth_source == current_app.PGADMIN_DEFAULT_AUTH_SOURCE:
if usr.auth_source == INTERNAL:
non_editable_params = ('username', 'email')
for f in non_editable_params:
@ -463,7 +466,7 @@ def role(rid):
)
def auth_sources():
sources = []
for source in current_app.PGADMIN_SUPPORTED_AUTH_SOURCE:
for source in SUPPORTED_AUTH_SOURCES:
sources.append({'label': source, 'value': source})
return ajax_response(

View File

@ -47,3 +47,12 @@ ERROR_FETCHING_ROLE_INFORMATION = gettext(
'Error fetching role information from the database server.')
ERROR_FETCHING_DATA = gettext('Unable to fetch data.')
# Authentication Sources
INTERNAL = 'internal'
LDAP = 'ldap'
KERBEROS = 'kerberos'
SUPPORTED_AUTH_SOURCES = [INTERNAL,
LDAP,
KERBEROS]

View File

@ -1,8 +1,9 @@
import config
from flask import current_app
from flask import current_app, session
from flask_login import current_user
from pgadmin.model import db, User, Server
from pgadmin.utils.crypto import encrypt, decrypt
from pgadmin.utils.constants import KERBEROS
MASTERPASS_CHECK_TEXT = 'ideas are bulletproof'
@ -32,6 +33,11 @@ def get_crypt_key():
elif config.MASTER_PASSWORD_REQUIRED \
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
else:
return True, enc_key

View File

@ -101,7 +101,8 @@ class TestClient(testing.FlaskClient):
return csrf_token
def login(self, email, password, _follow_redirects=False):
def login(self, email, password, _follow_redirects=False,
headers=None):
if config.SERVER_MODE is True:
res = self.get('/login', follow_redirects=True)
csrf_token = self.fetch_csrf(res)
@ -113,7 +114,8 @@ class TestClient(testing.FlaskClient):
email=email, password=password,
csrf_token=csrf_token,
),
follow_redirects=_follow_redirects
follow_redirects=_follow_redirects,
headers=headers
)
self.csrf_token = csrf_token

View File

@ -117,9 +117,9 @@ if config.SERVER_MODE is True:
app.config['WTF_CSRF_ENABLED'] = True
# Authentication sources
app.PGADMIN_DEFAULT_AUTH_SOURCE = 'internal'
app.PGADMIN_EXTERNAL_AUTH_SOURCE = 'ldap'
app.test_client_class = TestClient
test_client = app.test_client()
test_client.setApp(app)