mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
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:
parent
9a47e574e3
commit
6ead597b43
@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
|
|||||||
New features
|
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
|
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 #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 #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 #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 #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 #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.
|
| `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.
|
||||||
|
@ -43,3 +43,4 @@ cryptography<=3.0;
|
|||||||
sshtunnel>=0.1.5
|
sshtunnel>=0.1.5
|
||||||
ldap3>=2.5.1
|
ldap3>=2.5.1
|
||||||
Flask-BabelEx>=0.9.4
|
Flask-BabelEx>=0.9.4
|
||||||
|
gssapi>=1.6.11
|
||||||
|
@ -535,7 +535,7 @@ ENHANCED_COOKIE_PROTECTION = True
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
# Default setting is internal
|
# Default setting is internal
|
||||||
# External Supported Sources: ldap
|
# External Supported Sources: ldap, kerberos
|
||||||
# Multiple authentication can be achieved by setting this parameter to
|
# Multiple authentication can be achieved by setting this parameter to
|
||||||
# ['ldap', 'internal']. pgAdmin will authenticate the user with ldap first,
|
# ['ldap', 'internal']. pgAdmin will authenticate the user with ldap first,
|
||||||
# in case of failure internal authentication will be done.
|
# in case of failure internal authentication will be done.
|
||||||
@ -618,6 +618,26 @@ LDAP_CA_CERT_FILE = ''
|
|||||||
LDAP_CERT_FILE = ''
|
LDAP_CERT_FILE = ''
|
||||||
LDAP_KEY_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
|
# Local config settings
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -35,6 +35,9 @@ else:
|
|||||||
import config
|
import config
|
||||||
from pgadmin import create_app
|
from pgadmin import create_app
|
||||||
from pgadmin.utils import u_encode, fs_encoding, file_quote
|
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
|
# 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
|
# as it turns out that putting it in the config files isn't a great idea
|
||||||
from pgadmin.model import SCHEMA_VERSION
|
from pgadmin.model import SCHEMA_VERSION
|
||||||
@ -96,15 +99,11 @@ if config.SERVER_MODE:
|
|||||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
|
|
||||||
# Authentication sources
|
# Authentication sources
|
||||||
app.PGADMIN_DEFAULT_AUTH_SOURCE = 'internal'
|
|
||||||
app.PGADMIN_SUPPORTED_AUTH_SOURCE = ['internal', 'ldap']
|
|
||||||
if len(config.AUTHENTICATION_SOURCES) > 0:
|
if len(config.AUTHENTICATION_SOURCES) > 0:
|
||||||
app.PGADMIN_EXTERNAL_AUTH_SOURCE = config.AUTHENTICATION_SOURCES[0]
|
app.PGADMIN_EXTERNAL_AUTH_SOURCE = config.AUTHENTICATION_SOURCES[0]
|
||||||
else:
|
else:
|
||||||
app.PGADMIN_EXTERNAL_AUTH_SOURCE = app.PGADMIN_DEFAULT_AUTH_SOURCE
|
app.PGADMIN_EXTERNAL_AUTH_SOURCE = INTERNAL
|
||||||
|
|
||||||
app.logger.debug(
|
|
||||||
"Authentication Source: %s" % app.PGADMIN_DEFAULT_AUTH_SOURCE)
|
|
||||||
|
|
||||||
# Start the web server. The port number should have already been set by the
|
# 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
|
# runtime if we're running in desktop mode, otherwise we'll just use the
|
||||||
|
@ -43,6 +43,7 @@ from pgadmin.utils.ajax import internal_server_error, make_json_response
|
|||||||
from pgadmin.utils.csrf import pgCSRFProtect
|
from pgadmin.utils.csrf import pgCSRFProtect
|
||||||
from pgadmin import authenticate
|
from pgadmin import authenticate
|
||||||
from pgadmin.utils.security_headers import SecurityHeaders
|
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
|
# 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
|
# 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)
|
abort(401)
|
||||||
login_user(user)
|
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
|
# if the server is restarted the in memory key will be lost
|
||||||
# but the user session may still be active. Logout the user
|
# but the user session may still be active. Logout the user
|
||||||
# to get the key again when login
|
# to get the key again when login
|
||||||
if config.SERVER_MODE and current_user.is_authenticated and \
|
if config.SERVER_MODE and current_user.is_authenticated and \
|
||||||
|
app.PGADMIN_EXTERNAL_AUTH_SOURCE != \
|
||||||
|
KERBEROS and \
|
||||||
current_app.keyManager.get() is None and \
|
current_app.keyManager.get() is None and \
|
||||||
request.endpoint not in ('security.login', 'security.logout'):
|
request.endpoint not in ('security.login', 'security.logout'):
|
||||||
logout_user()
|
logout_user()
|
||||||
|
@ -11,16 +11,21 @@
|
|||||||
|
|
||||||
import flask
|
import flask
|
||||||
import pickle
|
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_babelex import gettext
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
from flask_security.views import _security, _ctx
|
from flask_security.views import _security, _ctx
|
||||||
from flask_security.utils import config_value, get_post_logout_redirect, \
|
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
|
from flask import session
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from pgadmin.utils import PgAdminModule
|
from pgadmin.utils import PgAdminModule
|
||||||
|
from pgadmin.utils.constants import KERBEROS
|
||||||
|
from pgadmin.utils.csrf import pgCSRFProtect
|
||||||
|
|
||||||
from .registry import AuthSourceRegistry
|
from .registry import AuthSourceRegistry
|
||||||
|
|
||||||
MODULE_NAME = 'authenticate'
|
MODULE_NAME = 'authenticate'
|
||||||
@ -28,12 +33,34 @@ MODULE_NAME = 'authenticate'
|
|||||||
|
|
||||||
class AuthenticateModule(PgAdminModule):
|
class AuthenticateModule(PgAdminModule):
|
||||||
def get_exposed_url_endpoints(self):
|
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 = 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'])
|
@blueprint.route('/login', endpoint='login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
"""
|
"""
|
||||||
@ -56,15 +83,24 @@ def login():
|
|||||||
if status:
|
if status:
|
||||||
# Login the user
|
# Login the user
|
||||||
status, msg = auth_obj.login()
|
status, msg = auth_obj.login()
|
||||||
|
current_auth_obj = auth_obj.as_dict()
|
||||||
if not status:
|
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')
|
flash(gettext(msg), 'danger')
|
||||||
return flask.redirect(get_post_logout_redirect())
|
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())
|
return flask.redirect(get_post_login_redirect())
|
||||||
|
|
||||||
|
elif isinstance(msg, Response):
|
||||||
|
return msg
|
||||||
flash(gettext(msg), 'danger')
|
flash(gettext(msg), 'danger')
|
||||||
return flask.redirect(get_post_logout_redirect())
|
response = flask.redirect(get_post_logout_redirect())
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class AuthSourceManager():
|
class AuthSourceManager():
|
||||||
@ -75,6 +111,7 @@ class AuthSourceManager():
|
|||||||
self.auth_sources = sources
|
self.auth_sources = sources
|
||||||
self.source = None
|
self.source = None
|
||||||
self.source_friendly_name = None
|
self.source_friendly_name = None
|
||||||
|
self.current_source = None
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
"""
|
"""
|
||||||
@ -84,9 +121,17 @@ class AuthSourceManager():
|
|||||||
res = dict()
|
res = dict()
|
||||||
res['source_friendly_name'] = self.source_friendly_name
|
res['source_friendly_name'] = self.source_friendly_name
|
||||||
res['auth_sources'] = self.auth_sources
|
res['auth_sources'] = self.auth_sources
|
||||||
|
res['current_source'] = self.current_source
|
||||||
|
|
||||||
return res
|
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):
|
def set_source(self, source):
|
||||||
self.source = source
|
self.source = source
|
||||||
|
|
||||||
@ -115,9 +160,33 @@ class AuthSourceManager():
|
|||||||
msg = None
|
msg = None
|
||||||
for src in self.auth_sources:
|
for src in self.auth_sources:
|
||||||
source = get_auth_sources(src)
|
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)
|
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:
|
if status:
|
||||||
self.set_source(source)
|
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
|
||||||
return status, msg
|
return status, msg
|
||||||
|
|
||||||
@ -125,6 +194,9 @@ class AuthSourceManager():
|
|||||||
status, msg = self.source.login(self.form)
|
status, msg = self.source.login(self.form)
|
||||||
if status:
|
if status:
|
||||||
self.set_source_friendly_name(self.source.get_friendly_name())
|
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
|
return status, msg
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ from flask_babelex import gettext
|
|||||||
from .registry import AuthSourceRegistry
|
from .registry import AuthSourceRegistry
|
||||||
from pgadmin.model import User
|
from pgadmin.model import User
|
||||||
from pgadmin.utils.validation_utils import validate_email
|
from pgadmin.utils.validation_utils import validate_email
|
||||||
|
from pgadmin.utils.constants import INTERNAL
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(AuthSourceRegistry)
|
@six.add_metaclass(AuthSourceRegistry)
|
||||||
@ -31,7 +32,11 @@ class BaseAuthentication(object):
|
|||||||
'INVALID_EMAIL': gettext('Email/Username is not valid')
|
'INVALID_EMAIL': gettext('Email/Username is not valid')
|
||||||
}
|
}
|
||||||
|
|
||||||
@abstractproperty
|
@abstractmethod
|
||||||
|
def get_source_name(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
def get_friendly_name(self):
|
def get_friendly_name(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -82,6 +87,9 @@ class BaseAuthentication(object):
|
|||||||
|
|
||||||
class InternalAuthentication(BaseAuthentication):
|
class InternalAuthentication(BaseAuthentication):
|
||||||
|
|
||||||
|
def get_source_name(self):
|
||||||
|
return INTERNAL
|
||||||
|
|
||||||
def get_friendly_name(self):
|
def get_friendly_name(self):
|
||||||
return gettext("internal")
|
return gettext("internal")
|
||||||
|
|
||||||
|
138
web/pgadmin/authenticate/kerberos.py
Normal file
138
web/pgadmin/authenticate/kerberos.py
Normal 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}
|
@ -23,6 +23,7 @@ from .internal import BaseAuthentication
|
|||||||
from pgadmin.model import User, ServerGroup, db, Role
|
from pgadmin.model import User, ServerGroup, db, Role
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from pgadmin.tools.user_management import create_user
|
from pgadmin.tools.user_management import create_user
|
||||||
|
from pgadmin.utils.constants import LDAP
|
||||||
|
|
||||||
|
|
||||||
ERROR_SEARCHING_LDAP_DIRECTORY = "Error searching the LDAP directory: {}"
|
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):
|
class LDAPAuthentication(BaseAuthentication):
|
||||||
"""Ldap Authentication Class"""
|
"""Ldap Authentication Class"""
|
||||||
|
|
||||||
|
def get_source_name(self):
|
||||||
|
return LDAP
|
||||||
|
|
||||||
def get_friendly_name(self):
|
def get_friendly_name(self):
|
||||||
return gettext("ldap")
|
return gettext("ldap")
|
||||||
|
|
||||||
@ -151,7 +155,7 @@ class LDAPAuthentication(BaseAuthentication):
|
|||||||
'email': user_email,
|
'email': user_email,
|
||||||
'role': 2,
|
'role': 2,
|
||||||
'active': True,
|
'active': True,
|
||||||
'auth_source': 'ldap'
|
'auth_source': LDAP
|
||||||
})
|
})
|
||||||
|
|
||||||
return True, None
|
return True, None
|
||||||
|
@ -29,7 +29,7 @@ from flask_security.recoverable import reset_password_token_status, \
|
|||||||
generate_reset_password_token, update_password
|
generate_reset_password_token, update_password
|
||||||
from flask_security.signals import reset_password_instructions_sent
|
from flask_security.signals import reset_password_instructions_sent
|
||||||
from flask_security.utils import config_value, do_flash, get_url, \
|
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 flask_security.views import _security, _commit, _ctx
|
||||||
from werkzeug.datastructures import MultiDict
|
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_masterpass_check_text, cleanup_master_password, get_crypt_key, \
|
||||||
set_crypt_key, process_masterpass_disabled
|
set_crypt_key, process_masterpass_disabled
|
||||||
from pgadmin.model import User
|
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:
|
try:
|
||||||
from flask_security.views import default_render_json
|
from flask_security.views import default_render_json
|
||||||
@ -280,7 +281,8 @@ class BrowserModule(PgAdminModule):
|
|||||||
'browser.check_master_password',
|
'browser.check_master_password',
|
||||||
'browser.set_master_password',
|
'browser.set_master_password',
|
||||||
'browser.reset_master_password',
|
'browser.reset_master_password',
|
||||||
'browser.lock_layout']
|
'browser.lock_layout'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
blueprint = BrowserModule(MODULE_NAME, __name__)
|
blueprint = BrowserModule(MODULE_NAME, __name__)
|
||||||
@ -539,6 +541,12 @@ class BrowserPluginModule(PgAdminModule):
|
|||||||
|
|
||||||
|
|
||||||
def _get_logout_url():
|
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(
|
return '{0}?next={1}'.format(
|
||||||
url_for('security.logout'), url_for(BROWSER_INDEX))
|
url_for('security.logout'), url_for(BROWSER_INDEX))
|
||||||
|
|
||||||
@ -664,13 +672,18 @@ def index():
|
|||||||
auth_only_internal = False
|
auth_only_internal = False
|
||||||
auth_source = []
|
auth_source = []
|
||||||
|
|
||||||
|
session['allow_save_password'] = True
|
||||||
|
|
||||||
if config.SERVER_MODE:
|
if config.SERVER_MODE:
|
||||||
if len(config.AUTHENTICATION_SOURCES) == 1\
|
if len(config.AUTHENTICATION_SOURCES) == 1\
|
||||||
and 'internal' in config.AUTHENTICATION_SOURCES:
|
and INTERNAL in config.AUTHENTICATION_SOURCES:
|
||||||
auth_only_internal = True
|
auth_only_internal = True
|
||||||
auth_source = session['_auth_source_manager_obj'][
|
auth_source = session['_auth_source_manager_obj'][
|
||||||
'source_friendly_name']
|
'source_friendly_name']
|
||||||
|
|
||||||
|
if session['_auth_source_manager_obj']['current_source'] == KERBEROS:
|
||||||
|
session['allow_save_password'] = False
|
||||||
|
|
||||||
response = Response(render_template(
|
response = Response(render_template(
|
||||||
MODULE_NAME + "/index.html",
|
MODULE_NAME + "/index.html",
|
||||||
username=current_user.username,
|
username=current_user.username,
|
||||||
@ -1086,7 +1099,7 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
|
|||||||
# Check the Authentication source of the User
|
# Check the Authentication source of the User
|
||||||
user = User.query.filter_by(
|
user = User.query.filter_by(
|
||||||
email=form.data['email'],
|
email=form.data['email'],
|
||||||
auth_source=current_app.PGADMIN_DEFAULT_AUTH_SOURCE
|
auth_source=INTERNAL
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import simplejson as json
|
import simplejson as json
|
||||||
import pgadmin.browser.server_groups as sg
|
import pgadmin.browser.server_groups as sg
|
||||||
from flask import render_template, request, make_response, jsonify, \
|
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_babelex import gettext
|
||||||
from flask_security import current_user, login_required
|
from flask_security import current_user, login_required
|
||||||
from pgadmin.browser.server_groups.servers.types import ServerType
|
from pgadmin.browser.server_groups.servers.types import ServerType
|
||||||
@ -1822,7 +1822,13 @@ class ServerNode(PGChildNodeView):
|
|||||||
_=gettext,
|
_=gettext,
|
||||||
service=server.service,
|
service=server.service,
|
||||||
prompt_tunnel_password=prompt_tunnel_password,
|
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:
|
else:
|
||||||
@ -1836,6 +1842,9 @@ class ServerNode(PGChildNodeView):
|
|||||||
errmsg=errmsg,
|
errmsg=errmsg,
|
||||||
service=server.service,
|
service=server.service,
|
||||||
_=gettext,
|
_=gettext,
|
||||||
|
allow_save_password=True if
|
||||||
|
config.ALLOW_SAVE_PASSWORD and
|
||||||
|
session['allow_save_password'] else False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
<input class="custom-control-input" id="save_password" name="save_password" type="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>
|
<label class="custom-control-label" for="save_password">{{ _('Save Password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
<input class="custom-control-input" id="save_tunnel_password" name="save_tunnel_password" type="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>
|
<label class="custom-control-label" for="save_tunnel_password" class="ml-1">{{ _('Save Password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
<input class="custom-control-input" id="save_password" name="save_password" type="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>
|
<label class="custom-control-label" for="save_password" class="ml-1">{{ _('Save Password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
16
web/pgadmin/browser/templates/browser/kerberos_login.html
Normal file
16
web/pgadmin/browser/templates/browser/kerberos_login.html
Normal 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 %}
|
16
web/pgadmin/browser/templates/browser/kerberos_logout.html
Normal file
16
web/pgadmin/browser/templates/browser/kerberos_logout.html
Normal 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 %}
|
104
web/pgadmin/browser/tests/test_kerberos_with_mocking.py
Normal file
104
web/pgadmin/browser/tests/test_kerberos_with_mocking.py
Normal 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)
|
@ -25,7 +25,7 @@ from pgadmin.utils import PgAdminModule
|
|||||||
from pgadmin.utils.ajax import make_json_response, bad_request, \
|
from pgadmin.utils.ajax import make_json_response, bad_request, \
|
||||||
internal_server_error, unauthorized
|
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.model import Server, User
|
||||||
from pgadmin.utils.driver import get_driver
|
from pgadmin.utils.driver import get_driver
|
||||||
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
|
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
|
||||||
@ -402,6 +402,9 @@ def _init_query_tool(trans_id, connect, sgid, sid, did, **kwargs):
|
|||||||
username=user,
|
username=user,
|
||||||
errmsg=msg,
|
errmsg=msg,
|
||||||
_=gettext,
|
_=gettext,
|
||||||
|
allow_save_password=True if
|
||||||
|
ALLOW_SAVE_PASSWORD and
|
||||||
|
session['allow_save_password'] else False,
|
||||||
)
|
)
|
||||||
), '', ''
|
), '', ''
|
||||||
else:
|
else:
|
||||||
|
@ -13,7 +13,7 @@ import simplejson as json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from flask import render_template, request, \
|
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_babelex import gettext as _
|
||||||
from flask_security import login_required, roles_required, current_user
|
from flask_security import login_required, roles_required, current_user
|
||||||
from flask_security.utils import encrypt_password
|
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, \
|
from pgadmin.utils.ajax import make_response as ajax_response, \
|
||||||
make_json_response, bad_request, internal_server_error, forbidden
|
make_json_response, bad_request, internal_server_error, forbidden
|
||||||
from pgadmin.utils.csrf import pgCSRFProtect
|
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.utils.validation_utils import validate_email
|
||||||
from pgadmin.model import db, Role, User, UserPreference, Server, \
|
from pgadmin.model import db, Role, User, UserPreference, Server, \
|
||||||
ServerGroup, Process, Setting
|
ServerGroup, Process, Setting
|
||||||
@ -167,11 +168,13 @@ def current_user_info():
|
|||||||
config.SERVER_MODE is True
|
config.SERVER_MODE is True
|
||||||
else 'postgres'
|
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',
|
else 'false',
|
||||||
allow_save_tunnel_password='true'
|
allow_save_tunnel_password='true' if
|
||||||
if config.ALLOW_SAVE_TUNNEL_PASSWORD else 'false',
|
config.ALLOW_SAVE_TUNNEL_PASSWORD and session[
|
||||||
auth_sources=config.AUTHENTICATION_SOURCES,
|
'allow_save_password'] else 'false',
|
||||||
|
auth_sources=config.AUTHENTICATION_SOURCES
|
||||||
),
|
),
|
||||||
status=200,
|
status=200,
|
||||||
mimetype=MIMETYPE_APP_JS
|
mimetype=MIMETYPE_APP_JS
|
||||||
@ -254,10 +257,10 @@ def _create_new_user(new_data):
|
|||||||
:return: Return new created user.
|
:return: Return new created user.
|
||||||
"""
|
"""
|
||||||
auth_source = new_data['auth_source'] if 'auth_source' in new_data \
|
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 = new_data['username'] if \
|
||||||
'username' in new_data and auth_source != \
|
'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
|
email = new_data['email'] if 'email' in new_data else None
|
||||||
password = new_data['password'] if 'password' 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):
|
def create_user(data):
|
||||||
if 'auth_source' in data and data['auth_source'] != \
|
if 'auth_source' in data and data['auth_source'] != \
|
||||||
current_app.PGADMIN_DEFAULT_AUTH_SOURCE:
|
INTERNAL:
|
||||||
req_params = ('username', 'role', 'active', 'auth_source')
|
req_params = ('username', 'role', 'active', 'auth_source')
|
||||||
else:
|
else:
|
||||||
req_params = ('email', 'role', 'active', 'newPassword',
|
req_params = ('email', 'role', 'active', 'newPassword',
|
||||||
@ -380,7 +383,7 @@ def update(uid):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Username and email can not be changed for internal users
|
# 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')
|
non_editable_params = ('username', 'email')
|
||||||
|
|
||||||
for f in non_editable_params:
|
for f in non_editable_params:
|
||||||
@ -463,7 +466,7 @@ def role(rid):
|
|||||||
)
|
)
|
||||||
def auth_sources():
|
def auth_sources():
|
||||||
sources = []
|
sources = []
|
||||||
for source in current_app.PGADMIN_SUPPORTED_AUTH_SOURCE:
|
for source in SUPPORTED_AUTH_SOURCES:
|
||||||
sources.append({'label': source, 'value': source})
|
sources.append({'label': source, 'value': source})
|
||||||
|
|
||||||
return ajax_response(
|
return ajax_response(
|
||||||
|
@ -47,3 +47,12 @@ ERROR_FETCHING_ROLE_INFORMATION = gettext(
|
|||||||
'Error fetching role information from the database server.')
|
'Error fetching role information from the database server.')
|
||||||
|
|
||||||
ERROR_FETCHING_DATA = gettext('Unable to fetch data.')
|
ERROR_FETCHING_DATA = gettext('Unable to fetch data.')
|
||||||
|
|
||||||
|
# Authentication Sources
|
||||||
|
INTERNAL = 'internal'
|
||||||
|
LDAP = 'ldap'
|
||||||
|
KERBEROS = 'kerberos'
|
||||||
|
|
||||||
|
SUPPORTED_AUTH_SOURCES = [INTERNAL,
|
||||||
|
LDAP,
|
||||||
|
KERBEROS]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import config
|
import config
|
||||||
from flask import current_app
|
from flask import current_app, session
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from pgadmin.model import db, User, Server
|
from pgadmin.model import db, User, Server
|
||||||
from pgadmin.utils.crypto import encrypt, decrypt
|
from pgadmin.utils.crypto import encrypt, decrypt
|
||||||
|
from pgadmin.utils.constants import KERBEROS
|
||||||
|
|
||||||
|
|
||||||
MASTERPASS_CHECK_TEXT = 'ideas are bulletproof'
|
MASTERPASS_CHECK_TEXT = 'ideas are bulletproof'
|
||||||
@ -32,6 +33,11 @@ def get_crypt_key():
|
|||||||
elif config.MASTER_PASSWORD_REQUIRED \
|
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
|
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:
|
else:
|
||||||
return True, enc_key
|
return True, enc_key
|
||||||
|
|
||||||
|
@ -101,7 +101,8 @@ class TestClient(testing.FlaskClient):
|
|||||||
|
|
||||||
return csrf_token
|
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:
|
if config.SERVER_MODE is True:
|
||||||
res = self.get('/login', follow_redirects=True)
|
res = self.get('/login', follow_redirects=True)
|
||||||
csrf_token = self.fetch_csrf(res)
|
csrf_token = self.fetch_csrf(res)
|
||||||
@ -113,7 +114,8 @@ class TestClient(testing.FlaskClient):
|
|||||||
email=email, password=password,
|
email=email, password=password,
|
||||||
csrf_token=csrf_token,
|
csrf_token=csrf_token,
|
||||||
),
|
),
|
||||||
follow_redirects=_follow_redirects
|
follow_redirects=_follow_redirects,
|
||||||
|
headers=headers
|
||||||
)
|
)
|
||||||
self.csrf_token = csrf_token
|
self.csrf_token = csrf_token
|
||||||
|
|
||||||
|
@ -117,9 +117,9 @@ if config.SERVER_MODE is True:
|
|||||||
app.config['WTF_CSRF_ENABLED'] = True
|
app.config['WTF_CSRF_ENABLED'] = True
|
||||||
|
|
||||||
# Authentication sources
|
# Authentication sources
|
||||||
app.PGADMIN_DEFAULT_AUTH_SOURCE = 'internal'
|
|
||||||
app.PGADMIN_EXTERNAL_AUTH_SOURCE = 'ldap'
|
app.PGADMIN_EXTERNAL_AUTH_SOURCE = 'ldap'
|
||||||
|
|
||||||
|
|
||||||
app.test_client_class = TestClient
|
app.test_client_class = TestClient
|
||||||
test_client = app.test_client()
|
test_client = app.test_client()
|
||||||
test_client.setApp(app)
|
test_client.setApp(app)
|
||||||
|
Loading…
Reference in New Issue
Block a user