mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Fixed CSRF security vulnerability issue. per Alvin Lindstam. Fixes #4217
Initial patch by: Khushboo Vashi Modified by: Ashesh Vashi and Murtuza Zabuawala
This commit is contained in:
parent
90a45557b9
commit
6f0eafb223
@ -15,6 +15,7 @@ Bug fixes
|
||||
| `Bug #4164 <https://redmine.postgresql.org/issues/4164>`_ - Fix file browser path issue which occurs when client is on Windows and server is on Mac/Linux.
|
||||
| `Bug #4194 <https://redmine.postgresql.org/issues/4194>`_ - Fix accessibility issue for menu navigation.
|
||||
| `Bug #4208 <https://redmine.postgresql.org/issues/4208>`_ - Update the UI logo.
|
||||
| `Bug #4217 <https://redmine.postgresql.org/issues/4217>`_ - Fixed CSRF security vulnerability issue.
|
||||
| `Bug #4218 <https://redmine.postgresql.org/issues/4218>`_ - Properly assign dropdownParent in Select2 controls.
|
||||
| `Bug #4219 <https://redmine.postgresql.org/issues/4219>`_ - Ensure popper.js is installed when needed.
|
||||
| `Bug #4227 <https://redmine.postgresql.org/issues/4227>`_ - Fixed Tab key navigation for Maintenance dialog.
|
||||
|
@ -123,6 +123,10 @@ if (not hasattr(builtins, 'SERVER_MODE')) or builtins.SERVER_MODE is None:
|
||||
else:
|
||||
SERVER_MODE = builtins.SERVER_MODE
|
||||
|
||||
# HTTP headers to search for CSRF token when it is not provided in the form.
|
||||
# Default is ['X-CSRFToken', 'X-CSRF-Token']
|
||||
WTF_CSRF_HEADERS = ['X-pgA-CSRFToken']
|
||||
|
||||
# User ID (email address) to use for the default user in desktop mode.
|
||||
# The default should be fine here, as it's not exposed in the app.
|
||||
DESKTOP_USER = 'pgadmin4@pgadmin.org'
|
||||
@ -141,9 +145,6 @@ DEFAULT_SERVER = '127.0.0.1'
|
||||
# environment by the runtime
|
||||
DEFAULT_SERVER_PORT = 5050
|
||||
|
||||
# Enable CSRF protection?
|
||||
CSRF_ENABLED = True
|
||||
|
||||
# Enable X-Frame-Option protection.
|
||||
# Set to one of "SAMEORIGIN", "ALLOW-FROM origin" or "" to disable.
|
||||
# Note that "DENY" is NOT supported (and will be silently ignored).
|
||||
|
@ -24,6 +24,7 @@ from flask_mail import Mail
|
||||
from flask_paranoid import Paranoid
|
||||
from flask_security import Security, SQLAlchemyUserDatastore, current_user
|
||||
from flask_security.utils import login_user
|
||||
|
||||
from werkzeug.datastructures import ImmutableDict
|
||||
from werkzeug.local import LocalProxy
|
||||
from werkzeug.utils import find_modules
|
||||
@ -37,6 +38,7 @@ from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
|
||||
from datetime import timedelta
|
||||
from pgadmin.setup import get_version, set_version
|
||||
from pgadmin.utils.ajax import internal_server_error
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
|
||||
|
||||
# If script is running under python3, it will not have the xrange function
|
||||
@ -367,7 +369,10 @@ def create_app(app_name=None):
|
||||
'CSRF_SESSION_KEY': config.CSRF_SESSION_KEY,
|
||||
'SECRET_KEY': config.SECRET_KEY,
|
||||
'SECURITY_PASSWORD_SALT': config.SECURITY_PASSWORD_SALT,
|
||||
'SESSION_COOKIE_DOMAIN': config.SESSION_COOKIE_DOMAIN
|
||||
'SESSION_COOKIE_DOMAIN': config.SESSION_COOKIE_DOMAIN,
|
||||
# CSRF Token expiration till session expires
|
||||
'WTF_CSRF_TIME_LIMIT': getattr(config, 'CSRF_TIME_LIMIT', None),
|
||||
'WTF_CSRF_METHODS': ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
}))
|
||||
|
||||
security.init_app(app, user_datastore)
|
||||
@ -706,8 +711,13 @@ def create_app(app_name=None):
|
||||
current_app.logger.error(e, exc_info=True)
|
||||
return e
|
||||
|
||||
##########################################################################
|
||||
# Protection against CSRF attacks
|
||||
##########################################################################
|
||||
with app.app_context():
|
||||
pgCSRFProtect.init_app(app)
|
||||
|
||||
##########################################################################
|
||||
# All done!
|
||||
##########################################################################
|
||||
|
||||
return app
|
||||
|
@ -37,6 +37,7 @@ from pgadmin import current_blueprint
|
||||
from pgadmin.settings import get_setting
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import make_json_response
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.browser.register_browser_preferences import \
|
||||
register_browser_preferences
|
||||
@ -478,6 +479,7 @@ class BrowserPluginModule(PgAdminModule):
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def index():
|
||||
"""Render and process the main browser window."""
|
||||
@ -561,6 +563,7 @@ def index():
|
||||
|
||||
|
||||
@blueprint.route("/js/utils.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def utils():
|
||||
layout = get_setting('Browser/Layout', default='')
|
||||
@ -627,6 +630,7 @@ def utils():
|
||||
|
||||
|
||||
@blueprint.route("/js/endpoints.js")
|
||||
@pgCSRFProtect.exempt
|
||||
def exposed_urls():
|
||||
return make_response(
|
||||
render_template('browser/js/endpoints.js'),
|
||||
@ -635,6 +639,7 @@ def exposed_urls():
|
||||
|
||||
|
||||
@blueprint.route("/js/error.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def error_js():
|
||||
return make_response(
|
||||
@ -642,42 +647,16 @@ def error_js():
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/node.js")
|
||||
@login_required
|
||||
def node_js():
|
||||
prefs = Preferences.module('paths')
|
||||
|
||||
pg_help_path_pref = prefs.preference('pg_help_path')
|
||||
pg_help_path = pg_help_path_pref.get()
|
||||
|
||||
edbas_help_path_pref = prefs.preference('edbas_help_path')
|
||||
edbas_help_path = edbas_help_path_pref.get()
|
||||
|
||||
return make_response(
|
||||
render_template('browser/js/node.js',
|
||||
pg_help_path=pg_help_path,
|
||||
edbas_help_path=edbas_help_path,
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/messages.js")
|
||||
@pgCSRFProtect.exempt
|
||||
def messages_js():
|
||||
return make_response(
|
||||
render_template('browser/js/messages.js', _=gettext),
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/collection.js")
|
||||
@login_required
|
||||
def collection_js():
|
||||
return make_response(
|
||||
render_template('browser/js/collection.js', _=gettext),
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/browser.css")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def browser_css():
|
||||
"""Render and return CSS snippets from the nodes and modules."""
|
||||
@ -711,6 +690,7 @@ def get_nodes():
|
||||
if hasattr(config, 'SECURITY_CHANGEABLE') and config.SECURITY_CHANGEABLE:
|
||||
@blueprint.route("/change_password", endpoint="change_password",
|
||||
methods=['GET', 'POST'])
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def change_password():
|
||||
"""View function which handles a change password request."""
|
||||
@ -794,6 +774,7 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
|
||||
|
||||
@blueprint.route("/reset_password", endpoint="forgot_password",
|
||||
methods=['GET', 'POST'])
|
||||
@pgCSRFProtect.exempt
|
||||
@anonymous_user_required
|
||||
def forgot_password():
|
||||
"""View function that handles a forgotten password request."""
|
||||
@ -860,10 +841,10 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
|
||||
methods=['GET', 'POST'],
|
||||
endpoint='reset_password'
|
||||
)
|
||||
@pgCSRFProtect.exempt
|
||||
@anonymous_user_required
|
||||
def reset_password(token):
|
||||
"""View function that handles a reset password request."""
|
||||
|
||||
expired, invalid, user = reset_password_token_status(token)
|
||||
|
||||
if invalid:
|
||||
|
@ -11,7 +11,8 @@ define('pgadmin.browser', [
|
||||
'sources/tree/tree',
|
||||
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
|
||||
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
|
||||
'sources/check_node_visibility', './toolbar', 'pgadmin.help', 'pgadmin.browser.utils',
|
||||
'sources/check_node_visibility', './toolbar', 'pgadmin.help',
|
||||
'sources/csrf', 'pgadmin.browser.utils',
|
||||
'wcdocker', 'jquery.contextmenu', 'jquery.aciplugin', 'jquery.acitree',
|
||||
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
|
||||
'pgadmin.browser.menu', 'pgadmin.browser.panel',
|
||||
@ -23,7 +24,7 @@ define('pgadmin.browser', [
|
||||
tree,
|
||||
gettext, url_for, require, $, _, S,
|
||||
Bootstrap, pgAdmin, Alertify, codemirror,
|
||||
checkNodeVisibility, toolBar, help
|
||||
checkNodeVisibility, toolBar, help, csrfToken
|
||||
) {
|
||||
window.jQuery = window.$ = $;
|
||||
// Some scripts do export their object in the window only.
|
||||
@ -36,6 +37,8 @@ define('pgadmin.browser', [
|
||||
var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
var select_object_msg = gettext('Please select an object in the tree view.');
|
||||
|
||||
csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
|
||||
|
||||
var panelEvents = {};
|
||||
panelEvents[wcDocker.EVENT.VISIBILITY_CHANGED] = function() {
|
||||
if (this.isVisible()) {
|
||||
@ -353,8 +356,8 @@ define('pgadmin.browser', [
|
||||
},
|
||||
save_current_layout: function(layout_id, docker) {
|
||||
if(docker) {
|
||||
var layout = docker.save();
|
||||
var settings = { setting: layout_id, value: layout };
|
||||
var layout = docker.save(),
|
||||
settings = { setting: layout_id, value: layout };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url_for('settings.store_bulk'),
|
||||
@ -525,11 +528,15 @@ define('pgadmin.browser', [
|
||||
pgBrowser.utils.registerScripts(this);
|
||||
pgBrowser.utils.addMenus(obj);
|
||||
|
||||
let headers = {};
|
||||
headers[pgAdmin.csrf_token_header] = pgAdmin.csrf_token;
|
||||
|
||||
// Ping the server every 5 minutes
|
||||
setInterval(function() {
|
||||
$.ajax({
|
||||
url: url_for('misc.cleanup'),
|
||||
type:'POST',
|
||||
headers: headers,
|
||||
})
|
||||
.done(function() {})
|
||||
.fail(function() {});
|
||||
|
@ -267,7 +267,8 @@ define([
|
||||
$.ajax({
|
||||
url: urlBase,
|
||||
type: 'GET',
|
||||
beforeSend: function() {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
|
||||
// Generate a timer for the request
|
||||
timer = setTimeout(function() {
|
||||
// notify user if request is taking longer than 1 second
|
||||
|
@ -12,6 +12,7 @@ import url_for from 'sources/url_for';
|
||||
import $ from 'jquery';
|
||||
import * as Alertify from 'pgadmin.alertifyjs';
|
||||
import * as SqlEditorUtils from 'sources/sqleditor_utils';
|
||||
|
||||
var modifyAnimation = require('sources/modify_animation');
|
||||
|
||||
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
@ -88,10 +89,14 @@ _.extend(pgBrowser, {
|
||||
|
||||
// Get and cache the preferences
|
||||
cache_preferences: function (modulesChanged) {
|
||||
var self = this;
|
||||
var self = this,
|
||||
headers = {};
|
||||
headers[pgAdmin.csrf_token_header] = pgAdmin.csrf_token;
|
||||
|
||||
setTimeout(function() {
|
||||
$.ajax({
|
||||
url: url_for('preferences.get_all'),
|
||||
headers: headers,
|
||||
})
|
||||
.done(function(res) {
|
||||
self.preferences_cache = res;
|
||||
|
@ -22,7 +22,6 @@ console.log(arguments);
|
||||
/* Show proper error dialog */
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
/*
|
||||
* Show loading spinner till every js module is loaded completely
|
||||
* Referenced url:
|
||||
|
@ -7,6 +7,7 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
define('pgadmin.browser.utils',
|
||||
['sources/pgadmin'], function(pgAdmin) {
|
||||
|
||||
@ -15,6 +16,8 @@ define('pgadmin.browser.utils',
|
||||
/* Add hooked-in panels by extensions */
|
||||
pgBrowser['panels_items'] = '{{ current_app.panels|tojson }}';
|
||||
|
||||
pgAdmin['csrf_token_header'] = '{{ current_app.config.get('WTF_CSRF_HEADERS')[0] }}';
|
||||
pgAdmin['csrf_token'] = '{{ csrf_token() }}';
|
||||
|
||||
// Define list of nodes on which Query tool option doesn't appears
|
||||
var unsupported_nodes = pgAdmin.unsupported_nodes = [
|
||||
|
@ -105,20 +105,13 @@ class ChangePasswordTestCase(BaseTestGenerator):
|
||||
)
|
||||
user_id = json.loads(response.data.decode('utf-8'))['id']
|
||||
# Logout the Administrator before login normal user
|
||||
test_utils.logout_tester_account(self.tester)
|
||||
response = self.tester.post(
|
||||
'/login',
|
||||
data=dict(
|
||||
email=self.username,
|
||||
password=self.password
|
||||
),
|
||||
follow_redirects=True
|
||||
)
|
||||
self.tester.logout()
|
||||
response = self.tester.login(self.username, self.password, True)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
# test the 'change password' test case
|
||||
utils.change_password(self)
|
||||
# Delete the normal user after changing it's password
|
||||
test_utils.logout_tester_account(self.tester)
|
||||
self.tester.logout()
|
||||
# Login the Administrator before deleting normal user
|
||||
test_utils.login_tester_account(self.tester)
|
||||
response = self.tester.delete(
|
||||
@ -131,4 +124,6 @@ class ChangePasswordTestCase(BaseTestGenerator):
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# Make sure - we're already logged out before running
|
||||
cls.tester.logout()
|
||||
test_utils.login_tester_account(cls.tester)
|
||||
|
@ -41,7 +41,7 @@ class TestLoginUserImage(BaseTestGenerator):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"Logout first if already logged in"
|
||||
utils.logout_tester_account(cls.tester)
|
||||
cls.tester.logout()
|
||||
|
||||
# No need to call baseclass setup function
|
||||
def setUp(self):
|
||||
@ -49,13 +49,8 @@ class TestLoginUserImage(BaseTestGenerator):
|
||||
|
||||
def runTest(self):
|
||||
# Login and check type of image in response
|
||||
response = self.tester.post(
|
||||
'/login', data=dict(
|
||||
email=self.email,
|
||||
password=self.password
|
||||
),
|
||||
follow_redirects=True
|
||||
)
|
||||
response = self.tester.login(self.email, self.password, True)
|
||||
|
||||
# Should have gravatar image
|
||||
if config.SHOW_GRAVATAR_IMAGE:
|
||||
self.assertIn(self.respdata, response.data.decode('utf8'))
|
||||
@ -69,4 +64,6 @@ class TestLoginUserImage(BaseTestGenerator):
|
||||
We need to again login the test client as soon as test scenarios
|
||||
finishes.
|
||||
"""
|
||||
# Make sure - we're already logged out
|
||||
cls.tester.logout()
|
||||
utils.login_tester_account(cls.tester)
|
||||
|
@ -8,7 +8,7 @@
|
||||
##########################################################################
|
||||
|
||||
import uuid
|
||||
|
||||
import config as app_config
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from regression.python_test_utils import test_utils as utils
|
||||
from regression.test_setup import config_data
|
||||
@ -28,6 +28,7 @@ class LoginTestCase(BaseTestGenerator):
|
||||
config_data['pgAdmin4_login_credentials']
|
||||
['login_username']),
|
||||
password=str(uuid.uuid4())[4:8],
|
||||
is_gravtar_image_check=False,
|
||||
respdata='Invalid password')),
|
||||
|
||||
# This test case validates the empty password field
|
||||
@ -35,6 +36,7 @@ class LoginTestCase(BaseTestGenerator):
|
||||
email=(
|
||||
config_data['pgAdmin4_login_credentials']
|
||||
['login_username']), password='',
|
||||
is_gravtar_image_check=False,
|
||||
respdata='Password not provided')),
|
||||
|
||||
# This test case validates blank email field
|
||||
@ -42,11 +44,13 @@ class LoginTestCase(BaseTestGenerator):
|
||||
email='', password=(
|
||||
config_data['pgAdmin4_login_credentials']
|
||||
['login_password']),
|
||||
is_gravtar_image_check=False,
|
||||
respdata='Email not provided')),
|
||||
|
||||
# This test case validates empty email and password
|
||||
('Empty_Credentials', dict(
|
||||
email='', password='',
|
||||
is_gravtar_image_check=False,
|
||||
respdata='Email not provided')),
|
||||
|
||||
# This test case validates the invalid/incorrect email id
|
||||
@ -55,12 +59,14 @@ class LoginTestCase(BaseTestGenerator):
|
||||
password=(
|
||||
config_data['pgAdmin4_login_credentials']
|
||||
['login_password']),
|
||||
is_gravtar_image_check=False,
|
||||
respdata='Specified user does not exist')),
|
||||
|
||||
# This test case validates invalid email and password
|
||||
('Invalid_Credentials', dict(
|
||||
email=str(uuid.uuid4())[1:8] + '@xyz.com',
|
||||
password=str(uuid.uuid4())[4:8],
|
||||
is_gravtar_image_check=False,
|
||||
respdata='Specified user does not exist')),
|
||||
|
||||
# This test case validates the valid/correct credentials and allow user
|
||||
@ -72,9 +78,13 @@ class LoginTestCase(BaseTestGenerator):
|
||||
password=(
|
||||
config_data['pgAdmin4_login_credentials']
|
||||
['login_password']),
|
||||
is_gravtar_image_check=True,
|
||||
respdata_without_gravtar=config_data['pgAdmin4_login_credentials']
|
||||
['login_username'],
|
||||
respdata='Gravatar image for %s' %
|
||||
config_data['pgAdmin4_login_credentials']
|
||||
['login_username']))
|
||||
['login_username']),
|
||||
)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@ -84,7 +94,7 @@ class LoginTestCase(BaseTestGenerator):
|
||||
logging in the client like invalid password, invalid emails,
|
||||
empty credentials etc.
|
||||
"""
|
||||
utils.logout_tester_account(cls.tester)
|
||||
cls.tester.logout()
|
||||
|
||||
# No need to call base class setup function
|
||||
def setUp(self):
|
||||
@ -92,15 +102,14 @@ class LoginTestCase(BaseTestGenerator):
|
||||
|
||||
def runTest(self):
|
||||
"""This function checks login functionality."""
|
||||
response = self.tester.post(
|
||||
'/login',
|
||||
data=dict(
|
||||
email=self.email,
|
||||
password=self.password
|
||||
),
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertTrue(self.respdata in response.data.decode('utf8'))
|
||||
res = self.tester.login(self.email, self.password, True)
|
||||
if self.is_gravtar_image_check:
|
||||
if app_config.SHOW_GRAVATAR_IMAGE:
|
||||
self.assertTrue(self.respdata in res.data.decode('utf8'))
|
||||
else:
|
||||
print(self.respdata_without_gravtar in res.data.decode('utf8'))
|
||||
else:
|
||||
self.assertTrue(self.respdata in res.data.decode('utf8'))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@ -108,4 +117,5 @@ class LoginTestCase(BaseTestGenerator):
|
||||
We need to again login the test client as soon as test scenarios
|
||||
finishes.
|
||||
"""
|
||||
cls.tester.logout()
|
||||
utils.login_tester_account(cls.tester)
|
||||
|
@ -11,7 +11,6 @@ import uuid
|
||||
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from regression.python_test_utils.test_utils import login_tester_account
|
||||
from regression.python_test_utils.test_utils import logout_tester_account
|
||||
from regression.test_setup import config_data
|
||||
|
||||
|
||||
@ -40,7 +39,7 @@ class ResetPasswordTestCase(BaseTestGenerator):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
logout_tester_account(cls.tester)
|
||||
cls.tester.logout()
|
||||
|
||||
# No need to call baseclass setup function
|
||||
def setUp(self):
|
||||
@ -50,8 +49,13 @@ class ResetPasswordTestCase(BaseTestGenerator):
|
||||
"""This function checks reset password functionality."""
|
||||
|
||||
response = self.tester.get('/browser/reset_password')
|
||||
self.assertTrue('Recover pgAdmin 4 Password' in response.data.decode(
|
||||
'utf-8'))
|
||||
self.assertTrue(
|
||||
'Recover Password' in response.data.decode('utf-8')
|
||||
)
|
||||
self.assertTrue(
|
||||
'Enter the email address for the user account you wish to '
|
||||
'recover the password for' in response.data.decode('utf-8')
|
||||
)
|
||||
response = self.tester.post(
|
||||
'/browser/reset_password', data=dict(email=self.email),
|
||||
follow_redirects=True)
|
||||
|
@ -13,15 +13,18 @@ def change_password(self):
|
||||
'/browser/change_password', follow_redirects=True
|
||||
)
|
||||
self.assertTrue(
|
||||
'pgAdmin 4 Password Change' in response.data.decode('utf-8')
|
||||
'Password Change' in response.data.decode('utf-8')
|
||||
)
|
||||
|
||||
csrf_token = self.tester.fetch_csrf(response)
|
||||
|
||||
response = self.tester.post(
|
||||
'/browser/change_password',
|
||||
data=dict(
|
||||
password=self.password,
|
||||
new_password=self.new_password,
|
||||
new_password_confirm=self.new_password_confirm
|
||||
new_password_confirm=self.new_password_confirm,
|
||||
csrf_token=csrf_token,
|
||||
),
|
||||
follow_redirects=True
|
||||
)
|
||||
|
@ -13,6 +13,7 @@ import pgadmin.utils.driver as driver
|
||||
from flask import url_for, render_template, Response, request
|
||||
from flask_babelex import gettext
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.utils.session import cleanup_session_files
|
||||
|
||||
@ -98,6 +99,7 @@ def ping():
|
||||
|
||||
# For Garbage Collecting closed connections
|
||||
@blueprint.route("/cleanup", methods=['POST'])
|
||||
@pgCSRFProtect.exempt
|
||||
def cleanup():
|
||||
driver.ping()
|
||||
# Cleanup session files.
|
||||
|
@ -9,8 +9,8 @@
|
||||
|
||||
define('misc.dependencies', [
|
||||
'sources/gettext', 'underscore', 'underscore.string', 'jquery', 'backbone',
|
||||
'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid',
|
||||
], function(gettext, _, S, $, Backbone, pgBrowser, Alertify, Backgrid) {
|
||||
'pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid',
|
||||
], function(gettext, _, S, $, Backbone, pgAdmin, pgBrowser, Alertify, Backgrid) {
|
||||
|
||||
if (pgBrowser.NodeDependencies)
|
||||
return pgBrowser.NodeDependencies;
|
||||
@ -150,7 +150,8 @@ define('misc.dependencies', [
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
beforeSend: function() {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
|
||||
// Generate a timer for the request
|
||||
timer = setTimeout(function() {
|
||||
// notify user if request is taking longer than 1 second
|
||||
|
@ -9,8 +9,8 @@
|
||||
|
||||
define('misc.dependents', [
|
||||
'sources/gettext', 'underscore', 'underscore.string', 'jquery', 'backbone',
|
||||
'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid',
|
||||
], function(gettext, _, S, $, Backbone, pgBrowser, Alertify, Backgrid) {
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid',
|
||||
], function(gettext, _, S, $, Backbone, pgAdmin, pgBrowser, Alertify, Backgrid) {
|
||||
|
||||
if (pgBrowser.NodeDependents)
|
||||
return pgBrowser.NodeDependents;
|
||||
@ -156,7 +156,8 @@ define('misc.dependents', [
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
beforeSend: function() {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
|
||||
// Generate a timer for the request
|
||||
timer = setTimeout(function() {
|
||||
// notify user if request is taking longer than 1 second
|
||||
|
@ -22,12 +22,14 @@ import loading_icon from 'acitree/image/load-root.gif';
|
||||
define([
|
||||
'jquery', 'underscore', 'underscore.string', 'pgadmin.alertifyjs',
|
||||
'sources/gettext', 'sources/url_for', 'dropzone', 'sources/pgadmin',
|
||||
'tablesorter',
|
||||
], function($, _, S, Alertify, gettext, url_for, Dropzone, pgAdmin) {
|
||||
'sources/csrf', 'tablesorter',
|
||||
], function($, _, S, Alertify, gettext, url_for, Dropzone, pgAdmin, csrfToken) {
|
||||
|
||||
/*---------------------------------------------------------
|
||||
Define functions used for various operations
|
||||
---------------------------------------------------------*/
|
||||
// Set the CSRF Token
|
||||
csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
|
||||
|
||||
// Return file extension
|
||||
var getFileExtension = function(name) {
|
||||
|
@ -123,7 +123,10 @@ define('misc.sql', [
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
beforeSend: function() {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(
|
||||
pgAdmin.csrf_token_header, pgAdmin.csrf_token
|
||||
);
|
||||
// Generate a timer for the request
|
||||
timer = setTimeout(function() {
|
||||
// Notify user if request is taking longer than 1 second
|
||||
|
@ -9,10 +9,10 @@
|
||||
|
||||
define('misc.statistics', [
|
||||
'sources/gettext', 'underscore', 'underscore.string', 'jquery', 'backbone',
|
||||
'pgadmin.browser', 'pgadmin.backgrid', 'alertify', 'sources/size_prettify',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backgrid', 'alertify', 'sources/size_prettify',
|
||||
'sources/misc/statistics/statistics',
|
||||
], function(
|
||||
gettext, _, S, $, Backbone, pgBrowser, Backgrid, Alertify, sizePrettify,
|
||||
gettext, _, S, $, Backbone, pgAdmin, pgBrowser, Backgrid, Alertify, sizePrettify,
|
||||
statisticsHelper
|
||||
) {
|
||||
|
||||
@ -235,7 +235,10 @@ define('misc.statistics', [
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
beforeSend: function() {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(
|
||||
pgAdmin.csrf_token_header, pgAdmin.csrf_token
|
||||
);
|
||||
// Generate a timer for the request
|
||||
timer = setTimeout(function() {
|
||||
// notify user if request is taking longer than 1 second
|
||||
|
@ -11,6 +11,7 @@ from pgadmin.utils.route import BaseTestGenerator
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
import config
|
||||
|
||||
|
||||
class ImportExportServersTestCase(BaseTestGenerator):
|
||||
@ -24,12 +25,20 @@ class ImportExportServersTestCase(BaseTestGenerator):
|
||||
]
|
||||
|
||||
def runTest(self):
|
||||
|
||||
if config.SERVER_MODE is True:
|
||||
self.skipTest(
|
||||
"Can not run import-export of servers in the SERVER mode."
|
||||
)
|
||||
|
||||
path = os.path.dirname(__file__)
|
||||
setup = os.path.realpath(os.path.join(path, "../../../setup.py"))
|
||||
|
||||
# Load the servers
|
||||
os.system("python %s --load-servers %s 2> %s" %
|
||||
(setup, os.path.join(path, "servers.json"), os.devnull))
|
||||
os.system(
|
||||
"python %s --load-servers %s 2> %s" %
|
||||
(setup, os.path.join(path, "servers.json"), os.devnull)
|
||||
)
|
||||
|
||||
# And dump them again
|
||||
tf = tempfile.NamedTemporaryFile(delete=False)
|
||||
|
60
web/pgadmin/static/js/csrf.js
Normal file
60
web/pgadmin/static/js/csrf.js
Normal file
@ -0,0 +1,60 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import $ from 'jquery';
|
||||
import Backbone from 'backbone';
|
||||
import axios from 'axios';
|
||||
|
||||
export function setPGCSRFToken(header, token) {
|
||||
if (!token) {
|
||||
// Throw error message.
|
||||
throw 'csrf-token meta tag has not been set';
|
||||
}
|
||||
|
||||
// Configure Backbone.sync to set CSRF-Token-header request header for
|
||||
// every requests except GET.
|
||||
var origBackboneSync = Backbone.sync;
|
||||
Backbone.sync = function(method, model, options) {
|
||||
options.beforeSend = function(xhr) {
|
||||
xhr.setRequestHeader(header, token);
|
||||
};
|
||||
|
||||
return origBackboneSync(method, model, options);
|
||||
};
|
||||
|
||||
// Configure Backbone.get to set 'X-CSRFToken' request header for
|
||||
// GET requests.
|
||||
var origBackboneGet = Backbone.get;
|
||||
Backbone.get = function(method, model, options) {
|
||||
options.beforeSend = function(xhr) {
|
||||
xhr.setRequestHeader(header, token);
|
||||
};
|
||||
|
||||
return origBackboneGet(method, model, options);
|
||||
};
|
||||
|
||||
// Configure jquery.ajax to set 'X-CSRFToken' request header for
|
||||
// every requests.
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(header, token);
|
||||
},
|
||||
});
|
||||
|
||||
// Configure axios to set 'X-CSRFToken' request header for
|
||||
// every requests.
|
||||
axios.interceptors.request.use(function (config) {
|
||||
config.headers[header] = token;
|
||||
|
||||
return config;
|
||||
}, function (error) {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
}
|
@ -57,13 +57,12 @@ class ExecuteQuery {
|
||||
if (sqlStatement.length <= 0) return;
|
||||
|
||||
const self = this;
|
||||
let service = axios.create({});
|
||||
self.explainPlan = explainPlan;
|
||||
|
||||
const sqlStatementWithAnalyze = ExecuteQuery.prepareAnalyzeSql(sqlStatement, explainPlan);
|
||||
|
||||
self.initializeExecutionOnSqlEditor(sqlStatementWithAnalyze);
|
||||
service.post(
|
||||
axios.post(
|
||||
this.generateURLReconnectionFlag(connect),
|
||||
JSON.stringify(sqlStatementWithAnalyze),
|
||||
{headers: {'Content-Type': 'application/json'}})
|
||||
@ -113,8 +112,7 @@ class ExecuteQuery {
|
||||
|
||||
poll() {
|
||||
const self = this;
|
||||
let service = axios.create({});
|
||||
service.get(
|
||||
axios.get(
|
||||
url_for('sqleditor.poll', {
|
||||
'trans_id': self.sqlServerObject.transId,
|
||||
})
|
||||
|
@ -146,7 +146,7 @@ _.extend(pgBrowser.browserTreeState, {
|
||||
}
|
||||
}
|
||||
console.warn(
|
||||
gettext('Error fetching the tree state."'), msg);
|
||||
gettext('Error fetching the tree state.'), msg);
|
||||
});
|
||||
},
|
||||
update_cache: function(item) {
|
||||
|
@ -43,8 +43,7 @@ export class BackupDialog extends Dialog {
|
||||
const baseUrl = this.url_for_utility_exists(sid, params);
|
||||
// Check pg_dump or pg_dumpall utility exists or not.
|
||||
let that = this;
|
||||
let service = axios.create({});
|
||||
service.get(
|
||||
axios.get(
|
||||
baseUrl
|
||||
).then(function(res) {
|
||||
if (!res.data.success) {
|
||||
|
@ -142,8 +142,7 @@ export class BackupDialogWrapper extends DialogWrapper {
|
||||
|
||||
this.setExtraParameters(selectedTreeNode, treeInfo);
|
||||
|
||||
let service = axios.create({});
|
||||
service.post(
|
||||
axios.post(
|
||||
baseUrl,
|
||||
this.view.model.toJSON()
|
||||
).then(function (res) {
|
||||
|
@ -353,7 +353,10 @@ define([
|
||||
$.ajax({
|
||||
url: baseUrl,
|
||||
method: 'GET',
|
||||
beforeSend: function() {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(
|
||||
pgAdmin.csrf_token_header, pgAdmin.csrf_token
|
||||
);
|
||||
// set cursor to progress before every poll.
|
||||
$('.debugger-container').addClass('show_progress');
|
||||
},
|
||||
|
@ -43,8 +43,7 @@ export class RestoreDialog extends Dialog {
|
||||
const baseUrl = this.url_for_utility_exists(sid);
|
||||
// Check pg_restore utility exists or not.
|
||||
let that = this;
|
||||
let service = axios.create({});
|
||||
service.get(
|
||||
axios.get(
|
||||
baseUrl
|
||||
).then(function(res) {
|
||||
if (!res.data.success) {
|
||||
|
@ -140,8 +140,7 @@ export class RestoreDialogWrapper extends DialogWrapper {
|
||||
|
||||
this.setExtraParameters(selectedTreeNode, treeInfo);
|
||||
|
||||
let service = axios.create({});
|
||||
service.post(
|
||||
axios.post(
|
||||
baseUrl,
|
||||
this.view.model.toJSON()
|
||||
).then(function (res) {
|
||||
|
@ -35,6 +35,7 @@ define('tools.querytool', [
|
||||
'sources/sqleditor/calculate_query_run_time',
|
||||
'sources/sqleditor/call_render_after_poll',
|
||||
'sources/sqleditor/query_tool_preferences',
|
||||
'sources/csrf',
|
||||
'sources/../bundle/slickgrid',
|
||||
'pgadmin.file_manager',
|
||||
'backgrid.sizeable.columns',
|
||||
@ -49,7 +50,7 @@ define('tools.querytool', [
|
||||
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler,
|
||||
GeometryViewer, historyColl, queryHist,
|
||||
keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid,
|
||||
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref) {
|
||||
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, csrfToken) {
|
||||
/* Return back, this has been called more than once */
|
||||
if (pgAdmin.SqlEditor)
|
||||
return pgAdmin.SqlEditor;
|
||||
@ -63,6 +64,8 @@ define('tools.querytool', [
|
||||
HistoryCollection = historyColl.default,
|
||||
QueryHistory = queryHist.default;
|
||||
|
||||
csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
|
||||
|
||||
var is_query_running = false;
|
||||
|
||||
// Defining Backbone view for the sql grid.
|
||||
@ -1892,6 +1895,7 @@ define('tools.querytool', [
|
||||
var self = this;
|
||||
this.container = container;
|
||||
this.state = {};
|
||||
this.csrf_token = pgAdmin.csrf_token;
|
||||
// Disable animation first
|
||||
modifyAnimation.modifyAlertifyAnimation();
|
||||
|
||||
|
@ -22,6 +22,7 @@ import config
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import make_response as ajax_response, \
|
||||
make_json_response, bad_request, internal_server_error
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
|
||||
from pgadmin.model import db, Role, User, UserPreference, Server, \
|
||||
ServerGroup, Process, Setting
|
||||
@ -136,6 +137,7 @@ def script():
|
||||
|
||||
|
||||
@blueprint.route("/current_user.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def current_user_info():
|
||||
return Response(
|
||||
|
43
web/pgadmin/utils/csrf.py
Normal file
43
web/pgadmin/utils/csrf.py
Normal file
@ -0,0 +1,43 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
#########################################################################
|
||||
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from flask import request, current_app
|
||||
|
||||
|
||||
class _PGCSRFProtect(CSRFProtect):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(_PGCSRFProtect, self).__init__(*args, **kwargs)
|
||||
|
||||
def init_app(self, app):
|
||||
res = super(_PGCSRFProtect, self).init_app(app)
|
||||
self._pg_csrf_exempt(app)
|
||||
|
||||
def _pg_csrf_exempt(self, app):
|
||||
"""Exempt some of the Views/blueprints from CSRF protection
|
||||
"""
|
||||
|
||||
exempt_views = [
|
||||
'flask.helpers.send_static_file',
|
||||
'flask_security.views.login',
|
||||
'flask_security.views.logout',
|
||||
'pgadmin.tools.translations',
|
||||
app.blueprints['redirects'],
|
||||
'pgadmin.browser.server_groups.servers.supported_servers-js',
|
||||
'pgadmin.tools.datagrid.initialize_query_tool',
|
||||
'pgadmin.tools.datagrid.panel',
|
||||
'pgadmin.tools.debugger.initialize_target',
|
||||
'pgadmin.tools.debugger.direct_new',
|
||||
]
|
||||
|
||||
for exempt in exempt_views:
|
||||
self.exempt(exempt)
|
||||
|
||||
|
||||
pgCSRFProtect = _PGCSRFProtect()
|
@ -153,7 +153,7 @@ class CachingSessionManager(SessionManager):
|
||||
with sess_lock:
|
||||
if sid in self._cache:
|
||||
session = self._cache[sid]
|
||||
if session.hmac_digest != digest:
|
||||
if session and session.hmac_digest != digest:
|
||||
session = None
|
||||
|
||||
# reset order in Dict
|
||||
|
124
web/regression/python_test_utils/csrf_test_client.py
Normal file
124
web/regression/python_test_utils/csrf_test_client.py
Normal file
@ -0,0 +1,124 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
import re
|
||||
import flask
|
||||
from flask import current_app, request, session, testing
|
||||
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.test import EnvironBuilder
|
||||
from flask_wtf.csrf import generate_csrf
|
||||
import config
|
||||
|
||||
|
||||
class RequestShim(object):
|
||||
"""
|
||||
A fake request that proxies cookie-related methods to a Flask test client.
|
||||
"""
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
def set_cookie(self, key, value='', *args, **kwargs):
|
||||
"Set the cookie on the Flask test client."
|
||||
server_name = current_app.config["SERVER_NAME"] or "localhost"
|
||||
return self.client.set_cookie(
|
||||
server_name, key=key, value=value, *args, **kwargs
|
||||
)
|
||||
|
||||
def delete_cookie(self, key, *args, **kwargs):
|
||||
"Delete the cookie on the Flask test client."
|
||||
server_name = current_app.config["SERVER_NAME"] or "localhost"
|
||||
return self.client.delete_cookie(
|
||||
server_name, key=key, *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class TestClient(testing.FlaskClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.csrf_token = None
|
||||
self.app = None
|
||||
super(TestClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def setApp(self, _app):
|
||||
self.app = _app
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
if len(args) > 0 and isinstance(args[0], (EnvironBuilder, dict)):
|
||||
return super(TestClient, self).open(*args, **kwargs)
|
||||
|
||||
data = kwargs.get('data', {})
|
||||
|
||||
if self.csrf_token is not None and not (
|
||||
'email' in data and
|
||||
'password' in data and
|
||||
'csrf_token' in data
|
||||
):
|
||||
api_key_headers = Headers({})
|
||||
api_key_headers[
|
||||
getattr(config, 'WTF_CSRF_HEADERS', ['X-CSRFToken'])[0]
|
||||
] = self.csrf_token
|
||||
headers = kwargs.pop('headers', Headers())
|
||||
headers.extend(api_key_headers)
|
||||
kwargs['headers'] = headers
|
||||
|
||||
return super(TestClient, self).open(*args, **kwargs)
|
||||
|
||||
def fetch_csrf(self, res):
|
||||
m = re.search(
|
||||
b'<input id="csrf_token" name="csrf_token" type="hidden"'
|
||||
b' value="([^"]*)">', res.data
|
||||
)
|
||||
|
||||
return m.group(1).decode("utf-8")
|
||||
|
||||
def generate_csrf_token(self, *args, **kwargs):
|
||||
# First, we'll wrap our request shim around the test client, so
|
||||
# that it will work correctly when Flask asks it to set a cookie.
|
||||
request = RequestShim(self)
|
||||
# Next, we need to look up any cookies that might already exist on
|
||||
# this test client, such as the secure cookie that
|
||||
# powers `flask.session`,
|
||||
# and make a test request context that has those cookies in it.
|
||||
environ_overrides = {}
|
||||
self.cookie_jar.inject_wsgi(environ_overrides)
|
||||
with self.app.test_request_context(
|
||||
"/login", environ_overrides=environ_overrides,
|
||||
):
|
||||
# Now, we call Flask-WTF's method of generating a CSRF token...
|
||||
csrf_token = generate_csrf()
|
||||
# ...which also sets a value in `flask.session`, so we need to
|
||||
# ask Flask to save that value to the cookie jar in the test
|
||||
# client. This is where we actually use that request shim we
|
||||
# made!
|
||||
self.app.save_session(flask.session, request)
|
||||
|
||||
return csrf_token
|
||||
|
||||
def login(self, email, password, _follow_redirects=False):
|
||||
if config.SERVER_MODE is True:
|
||||
res = self.get('/login', follow_redirects=True)
|
||||
csrf_token = self.fetch_csrf(res)
|
||||
else:
|
||||
csrf_token = self.generate_csrf_token()
|
||||
|
||||
res = self.post(
|
||||
'/login', data=dict(
|
||||
email=email, password=password,
|
||||
csrf_token=csrf_token,
|
||||
),
|
||||
follow_redirects=_follow_redirects
|
||||
)
|
||||
self.csrf_token = csrf_token
|
||||
|
||||
return res
|
||||
|
||||
def logout(self):
|
||||
res = self.get('/logout', follow_redirects=False)
|
||||
self.csrf_token = None
|
@ -51,8 +51,7 @@ def login_tester_account(tester):
|
||||
os.environ['PGADMIN_SETUP_PASSWORD']:
|
||||
email = os.environ['PGADMIN_SETUP_EMAIL']
|
||||
password = os.environ['PGADMIN_SETUP_PASSWORD']
|
||||
tester.post('/login', data=dict(email=email, password=password),
|
||||
follow_redirects=True)
|
||||
tester.login(email, password)
|
||||
else:
|
||||
from regression.runtests import app_starter
|
||||
print("Unable to login test client, email and password not found.",
|
||||
@ -61,18 +60,6 @@ def login_tester_account(tester):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def logout_tester_account(tester):
|
||||
"""
|
||||
This function logout the test account
|
||||
|
||||
:param tester: test client
|
||||
:type tester: flask test client object
|
||||
:return: None
|
||||
"""
|
||||
|
||||
tester.get('/logout')
|
||||
|
||||
|
||||
def get_config_data():
|
||||
"""This function reads the server data from config_data"""
|
||||
server_data = []
|
||||
@ -802,7 +789,8 @@ def _cleanup(tester, app_starter):
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
finally:
|
||||
# Logout the test client
|
||||
logout_tester_account(tester)
|
||||
tester.logout()
|
||||
|
||||
# Remove SQLite db file
|
||||
remove_db_file()
|
||||
if app_starter:
|
||||
|
@ -20,11 +20,13 @@ import sys
|
||||
import traceback
|
||||
import json
|
||||
import random
|
||||
import flask
|
||||
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
@ -57,6 +59,7 @@ if config.SERVER_MODE is True:
|
||||
config.SECURITY_CHANGEABLE = True
|
||||
config.SECURITY_POST_CHANGE_VIEW = 'browser.change_password'
|
||||
|
||||
|
||||
from regression import test_setup
|
||||
from regression.feature_utils.app_starter import AppStarter
|
||||
|
||||
@ -95,6 +98,7 @@ from pgadmin.model import SCHEMA_VERSION
|
||||
|
||||
# Delay the import test_utils as it needs updated config.SQLITE_PATH
|
||||
from regression.python_test_utils import test_utils
|
||||
from regression.python_test_utils.csrf_test_client import TestClient
|
||||
|
||||
config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION
|
||||
|
||||
@ -105,16 +109,19 @@ config.CONSOLE_LOG_LEVEL = WARNING
|
||||
|
||||
# Create the app
|
||||
app = create_app()
|
||||
app.config['WTF_CSRF_ENABLED'] = False
|
||||
|
||||
app.PGADMIN_KEY = ''
|
||||
app.config.update({'SESSION_COOKIE_DOMAIN': None})
|
||||
test_client = app.test_client()
|
||||
driver = None
|
||||
app_starter = None
|
||||
handle_cleanup = None
|
||||
app.PGADMIN_RUNTIME = True
|
||||
if config.SERVER_MODE is True:
|
||||
app.PGADMIN_RUNTIME = False
|
||||
app.config['WTF_CSRF_ENABLED'] = True
|
||||
app.test_client_class = TestClient
|
||||
test_client = app.test_client()
|
||||
test_client.setApp(app)
|
||||
|
||||
setattr(unittest.result.TestResult, "passed", [])
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user