Files
pgadmin4/web/pgadmin/about/__init__.py
T
Ashesh Vashi 58c531d831 Act only on the internal account when accounts from other sources share its email or username. #10676
The same email or username may belong to accounts from several auth
sources. Flask-Security looks the account up by email alone, and took
whichever row came first:

- Internal login checked the password against that row, so an internal
  user could not log in when an LDAP or OAuth2 account with the same
  email was found first. Flask-Security's lookup now prefers the internal
  account; ignoring case, it prefers the one matching exactly, then the
  oldest. A missing email or username identifies no account.
- A password reset was sent for whichever account had the email, and a
  reset token for an account from another source was accepted, giving it
  a local password and logging it in. Reset instructions are now sent
  for the internal account, and a token for any other account is
  refused.
- The About box showed the configuration to any user whose email was
  that of an administrator's account, whatever its source. It now checks
  the user's own role.

The test runner excluded every test module whose name started with one
on its desktop mode list, so excluding test_reset_password also excluded
test_reset_password_auth_source. Each entry now excludes only the module
it names, and the server mode only modules are listed.
2026-10-25 16:01:14 +05:30

144 lines
4.5 KiB
Python

##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module implementing the about box."""
from flask import Response, render_template, request
from flask_babel import gettext
from flask_security import current_user
from pgadmin.user_login_check import pga_login_required
from pgadmin.utils import PgAdminModule
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.constants import MIMETYPE_APP_JS
from pgadmin.utils.ajax import make_json_response
import config
from user_agents import parse
import platform
import re
import sys
MODULE_NAME = 'about'
class AboutModule(PgAdminModule):
def get_own_menuitems(self):
appname = config.APP_NAME
return {
'help_items': [
MenuItem(
name='mnu_about',
priority=999,
module="pgAdmin.About",
callback='about_show',
icon='fa fa-info-circle',
label=gettext('About %(appname)s', appname=appname)
)
]
}
def get_exposed_url_endpoints(self):
return ['about.index']
blueprint = AboutModule(MODULE_NAME, __name__, static_url_path='')
##########################################################################
# A test page
##########################################################################
@blueprint.route("/", endpoint='index')
@pga_login_required
def index():
"""Render the about box."""
info = {}
# Get OS , NW.js, Browser details
browser, os_details, electron_version = detect_browser(request)
admin = current_user.has_role('Administrator')
if electron_version:
info['electron'] = electron_version
if config.SERVER_MODE:
info['app_mode'] = gettext('Server')
else:
info['app_mode'] = gettext('Desktop')
info['commit_hash'] = getattr(config, 'COMMIT_HASH', None)
info['browser_details'] = browser
info['version'] = config.APP_VERSION
info['admin'] = admin
info['current_user'] = current_user.email
info['python_version'] = sys.version.split(" ", maxsplit=1)[0]
if admin:
settings = ""
info['os_details'] = os_details
info['log_file'] = config.LOG_FILE
# If external datbase is used do not display SQLITE_PATH
if not config.CONFIG_DATABASE_URI:
info['config_db'] = config.SQLITE_PATH
for setting in dir(config):
if not setting.startswith('_') and setting.isupper() and \
setting not in ['CSRF_SESSION_KEY',
'SECRET_KEY',
'SECURITY_PASSWORD_SALT',
'SECURITY_PASSWORD_HASH',
'ALLOWED_HOSTS',
'MAIL_PASSWORD',
'LDAP_BIND_PASSWORD',
'SECURITY_PASSWORD_HASH']:
if isinstance(getattr(config, setting), str):
settings = \
settings + '{} = "{}"\n'.format(
setting, getattr(config, setting))
else:
settings = \
settings + '{} = {}\n'.format(
setting, getattr(config, setting))
info['settings'] = settings
return make_json_response(
data=info,
status=200
)
def detect_browser(request):
"""This function returns the browser and os details"""
electron_version = None
agent = request.environ.get('HTTP_USER_AGENT')
try:
# available only for python 3.10 and above
os_release = platform.freedesktop_os_release()
os_details = os_release.get('PRETTY_NAME', '')
if os_details:
os_details += ', '
except Exception as _:
os_details = ''
os_details += parse(platform.platform()).ua_string
if 'Electron' in agent:
electron_version = re.findall('Electron/([\\d.]+\\d+)', agent)[0]
browser = re.findall(
'(opera|chrome|safari|firefox|msie|trident(?=/))/?\\s*([\\d.]+\\d+)',
agent, re.IGNORECASE)
if not browser:
browser = agent.split('/')[0]
else:
browser = " ".join(browser[0])
return browser, os_details, electron_version