Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15556f9f89 | ||
|
|
b76e0bea83 | ||
|
|
252429df50 | ||
|
|
d6c6b4a15a | ||
|
|
64d31b9d3f | ||
|
|
24ec882f45 | ||
|
|
d397959f6a | ||
|
|
da8cf14047 | ||
|
|
2451db8797 | ||
|
|
173b812b93 | ||
|
|
bdb8f20aed | ||
|
|
0d42f8026c | ||
|
|
c69ffaa91c | ||
|
|
173acd46a5 | ||
|
|
c39d257bf6 | ||
|
|
b3490ba3de | ||
|
|
4e5d8fe8e2 | ||
|
|
c4de5474a4 | ||
|
|
ebb5e3fe65 | ||
|
|
644624eabe | ||
|
|
9dbb74367d | ||
|
|
c07e52b9de | ||
|
|
78d55c7cc0 | ||
|
|
81752bb998 | ||
|
|
fb4afce629 | ||
|
|
6e8ebbd375 | ||
|
|
5c0ea0c012 | ||
|
|
ce7679c4bd | ||
|
|
b36d5d153b | ||
|
|
9e65c971a3 | ||
|
|
71919a4ce9 | ||
|
|
402dfd07db | ||
|
|
bf548dda3f | ||
|
|
2894c6cd14 | ||
|
|
3b69f92d70 | ||
|
|
82bd94e696 | ||
|
|
04328b76ba | ||
|
|
08b78fb6a1 | ||
|
|
23364464c2 | ||
|
|
d774a0ff67 | ||
|
|
5437a8adab | ||
|
|
6d52f2b911 | ||
|
|
2ca11c84e8 | ||
|
|
f561c0cee6 | ||
|
|
321b445a7e | ||
|
|
7ff85903e4 | ||
|
|
8b9c50ee5c | ||
|
|
4b6e9b031c | ||
|
|
e691885881 | ||
|
|
caf181d5e3 | ||
|
|
d90136d93d | ||
|
|
9a00a8a60b | ||
|
|
a588c2994d | ||
|
|
13704a4f34 | ||
|
|
912db8913f | ||
|
|
969a2961fe | ||
|
|
6b80523872 | ||
|
|
c16c593322 | ||
|
|
6a7339bbe4 | ||
|
|
41a20e0252 | ||
|
|
2dd075161d | ||
|
|
91075cc3f5 | ||
|
|
26c7d76d4f | ||
|
|
2185ea302d | ||
|
|
32defc91cc | ||
|
|
37bde4aaf4 | ||
|
|
1f76d207f2 | ||
|
|
8eab534c95 | ||
|
|
0eb3008426 | ||
|
|
dd187b9122 | ||
|
|
15252b0a26 | ||
|
|
aa95ff9f57 | ||
|
|
dad0d690db | ||
|
|
2fa849dc26 | ||
|
|
f340cd044b | ||
|
|
991f667f9f | ||
|
|
307637b969 | ||
|
|
91595593d3 | ||
|
|
12186a0fd6 | ||
|
|
9fdf68d559 | ||
|
|
8851cb0704 | ||
|
|
a936f708de | ||
|
|
dfa892d2a2 | ||
|
|
6f0eafb223 | ||
|
|
90a45557b9 | ||
|
|
2790d01c31 | ||
|
|
4d23bb24a5 | ||
|
|
361f7077fd | ||
|
|
dcd163ac02 | ||
|
|
ee37be56f9 | ||
|
|
f1ef7b0346 | ||
|
|
0ee50ac248 | ||
|
|
ea4dbd31e8 | ||
|
|
155768a2d7 | ||
|
|
da8da7bb49 | ||
|
|
5786c17569 | ||
|
|
941dea99f5 | ||
|
|
ab5f35da8d | ||
|
|
db4bb22cc6 | ||
|
|
2cd58efcdd | ||
|
|
ed8d1cde00 | ||
|
|
b7121bb2d2 | ||
|
|
3364350b01 | ||
|
|
4b421550d3 | ||
|
|
020d9bb018 | ||
|
|
eb189844e7 | ||
|
|
20c42d878b | ||
|
|
036bd66638 | ||
|
|
f02f91cbf1 | ||
|
|
34b59da526 |
No files matched your search
@@ -31,8 +31,8 @@ bundle-dev:
|
||||
linter:
|
||||
cd web && yarn run linter
|
||||
|
||||
check: install-node bundle linter
|
||||
cd web && pycodestyle --config=.pycodestyle . && yarn run karma start --single-run && python regression/runtests.py
|
||||
check: install-node bundle linter check-pep8
|
||||
cd web && yarn run karma start --single-run && python regression/runtests.py
|
||||
|
||||
check-pep8:
|
||||
pycodestyle --config=.pycodestyle docs/
|
||||
@@ -43,6 +43,9 @@ check-pep8:
|
||||
check-python:
|
||||
cd web && python regression/runtests.py --exclude feature_tests
|
||||
|
||||
check-resql:
|
||||
cd web && python regression/runtests.py --pkg resql --exclude feature_tests
|
||||
|
||||
check-feature: install-node bundle
|
||||
cd web && python regression/runtests.py --pkg feature_tests
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ Click the *Definition* tab to continue.
|
||||
Use the fields in the *Definition* tab to define the check constraint:
|
||||
|
||||
* Provide the expression that a row must satisfy in the *Check* field.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify this constraint
|
||||
is automatically inherited by a table's children. The default is *No*.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify that this
|
||||
constraint is not automatically inherited by a table's children. The default
|
||||
is *No*, meaning that the constraint will be inherited by any children.
|
||||
* Move the *Don't validate?* switch to the *No* position to skip validation of
|
||||
existing data; the constraint may not hold for all rows in the table. The
|
||||
default is *Yes*.
|
||||
|
||||
@@ -24,6 +24,13 @@ dialog, right-click on the *Servers* node of the tree control, and select
|
||||
|
||||
server_dialog
|
||||
|
||||
A master password is required to secure and later unlock saved server passwords.
|
||||
It is set by the user and can be disabled using config.
|
||||
|
||||
.. toctree::
|
||||
|
||||
master_password
|
||||
|
||||
After defining a server connection, right-click on the server name, and select
|
||||
*Connect to server* to authenticate with the server, and start using pgAdmin to
|
||||
manage objects that reside on the server.
|
||||
|
||||
@@ -68,6 +68,13 @@ Default: 80 or 443 (if TLS is enabled)
|
||||
Allows the port that the server listens on to be set to a specific value rather
|
||||
than using the default.
|
||||
|
||||
*PGADMIN_SERVER_JSON_FILE*
|
||||
|
||||
Default: /pgadmin4/servers.json
|
||||
|
||||
Override the default file path for the server definition list. See the
|
||||
/pgadmin4/servers.json mapped file below for more information.
|
||||
|
||||
*GUNICORN_THREADS*
|
||||
|
||||
Default: 25
|
||||
@@ -88,7 +95,6 @@ instances:
|
||||
This is the working directory in which pgAdmin stores session data, user files,
|
||||
configuration files, and it's configuration database. Mapping this directory
|
||||
onto the host machine gives you an easy way to maintain configuration between
|
||||
|
||||
invocations of the container.
|
||||
|
||||
*/pgadmin4/config_local.py*
|
||||
@@ -101,7 +107,9 @@ required.
|
||||
|
||||
If this file is mapped, server definitions found in it will be loaded at launch
|
||||
time. This allows connection information to be pre-loaded into the instance of
|
||||
pgAdmin in the container.
|
||||
pgAdmin in the container. Note that server definitions are only loaded on first
|
||||
launch, i.e. when the configuration database is created, and not on subsequent
|
||||
launches using the same configuration database.
|
||||
|
||||
*/certs/server.cert*
|
||||
|
||||
|
||||
@@ -45,11 +45,15 @@ are as follows:
|
||||
|
||||
* ``config_distro.py``: This file is read after ``config.py`` and is intended for
|
||||
packagers to change any settings that are required for their pgAdmin distribution.
|
||||
This may typically include certain paths and file locations.
|
||||
This may typically include certain paths and file locations. This file is optional,
|
||||
and may be created by packagers in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
* ``config_local.py``: This file is read after ``config_distro.py`` and is intended
|
||||
for end users to change any default or packaging specific settings that they may
|
||||
wish to adjust to meet local preferences or standards.
|
||||
wish to adjust to meet local preferences or standards.This file is optional,
|
||||
and may be created by users in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
.. note:: If the SERVER_MODE setting is changed in ``config_distro.py`` or ``config_local.py``,
|
||||
you will most likely need to re-set the LOG_FILE, SQLITE_PATH, SESSION_DB_PATH
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 111 KiB |
@@ -73,49 +73,49 @@ When using the syntax-highlighting SQL editors, the following shortcuts are avai
|
||||
:class: longtable
|
||||
:widths: 2 2 3
|
||||
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+==================+=====================================+
|
||||
| Alt+Left | Option+Left | Move to the beginning of the line |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Alt+Right | Option+Right | Move to the end of the line |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Alt+Left | Cmd+Option+Left | Move left one word |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Alt+Right | Cmd+Option+Right | Move right one word |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+/ | Cmd+/ | Comment selected code (Inline) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+. | Cmd+. | Uncomment selected code (Inline) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Shift+/ | Cmd+Shift+/ | Comment/Uncomment code (Block) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+A | Cmd+A | Select all |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+C | Cmd+C | Copy selected text to the clipboard |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+R | Cmd+R | Redo last edit un-done |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+V | Cmd+V | Paste text from the clipboard |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Z | Cmd+Z | Undo last edit |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Tab | Tab | Indent selected text |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Shift+Tab | Shift+Tab | Un-indent selected text |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Alt+G | Alt+G | Jump (to line:column) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Space | Ctrl+Space | Auto-complete |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+F | Cmd+F | Find |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+G | Cmd+G | Find next |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Shift+G | Cmd+Shift+G | Find previous |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Shift+F | Cmd+Shift+F | Replace |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+======================+=====================================+
|
||||
| Alt + Left | Option + Left | Move to the beginning of the line |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Alt + Right | Option + Right | Move to the end of the line |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Alt + Left | Cmd + Option + Left | Move left one word |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Alt + Right | Cmd + Option + Right | Move right one word |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + / | Cmd + / | Comment selected code (Inline) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + . | Cmd + . | Uncomment selected code (Inline) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Shift + / | Cmd + Shift + / | Comment/Uncomment code (Block) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + a | Cmd + a | Select all |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + c | Cmd + c | Copy selected text to the clipboard |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + r | Cmd + r | Redo last edit un-done |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + v | Cmd + v | Paste text from the clipboard |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + z | Cmd + z | Undo last edit |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Tab | Tab | Indent selected text |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Shift + Tab | Shift + Tab | Un-indent selected text |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Alt + g | Option + g | Jump (to line:column) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Space | Ctrl + Space | Auto-complete |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + f | Cmd + f | Find |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + g | Cmd + g | Find next |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Shift + g | Cmd + Shift + g | Find previous |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Shift + f | Cmd + Shift + f | Replace |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
|
||||
Query Tool
|
||||
**********
|
||||
@@ -133,7 +133,7 @@ When using the Query Tool, the following shortcuts are available:
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| F7 | F7 | EXPLAIN query |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| Shift+F7 | Shift+F7 | EXPLAIN ANALYZE query |
|
||||
| Shift + F7 | Shift + F7 | EXPLAIN ANALYZE query |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| F8 | F8 | Execute query to CSV file |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
@@ -190,11 +190,11 @@ When using the Debugger, the following shortcuts are available:
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| <accesskey> + s | <accesskey> + s | Stop |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| Alt + Shift + q | Alt + Shift + q | Enter or Edit values in Grid |
|
||||
| Alt + Shift + q | Option + Shift + q | Enter or Edit values in Grid |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
|
||||
Inner Panel Navigation
|
||||
**********************
|
||||
Inner Tab and Panel Navigation
|
||||
******************************
|
||||
|
||||
When using the Query Tool and Debugger, the following shortcuts are available
|
||||
for inner panel navigation:
|
||||
@@ -203,13 +203,15 @@ for inner panel navigation:
|
||||
:class: longtable
|
||||
:widths: 2 2 3
|
||||
|
||||
+--------------------------+---------------------------+------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+===========================+==============================+
|
||||
| Alt + Shift + Right Arrow| Alt + Shift + Right Arrow | Move to next inner panel |
|
||||
+--------------------------+---------------------------+------------------------------+
|
||||
| Alt + Shift + Left Arrow | Alt + Shift + Left Arrow | Move to previous inner panel |
|
||||
+--------------------------+---------------------------+------------------------------+
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+===========================+====================================+
|
||||
| Alt + Shift + ] | Alt + Shift + ] | Move to next tab within a panel |
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
| Alt + Shift + [ | Alt + Shift + [ | Move to previous tab within a panel|
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
| Alt + Shift + Tab | Alt + Shift + Tab | Move between inner panels |
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
|
||||
Access Key
|
||||
**********
|
||||
@@ -221,14 +223,14 @@ default access keys for supported browsers.
|
||||
:class: longtable
|
||||
:widths: 2 1 1 1
|
||||
|
||||
+-------------------+------------+------------+------------+
|
||||
| | Windows | Linux | Mac |
|
||||
+===================+============+============+============+
|
||||
| Internet Explorer | Alt | Alt | |
|
||||
+-------------------+------------+------------+------------+
|
||||
| Chrome | Alt | Alt | Ctrl+Alt |
|
||||
+-------------------+------------+------------+------------+
|
||||
| Firefox | Alt+Shift | Alt+Shift | Ctrl+Alt |
|
||||
+-------------------+------------+------------+------------+
|
||||
| Safari | Alt | | Ctrl+Alt |
|
||||
+-------------------+------------+------------+------------+
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| | Windows | Linux | Mac |
|
||||
+===================+=============+=============+===============+
|
||||
| Internet Explorer | Alt | Alt | |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| Chrome | Alt | Alt | Ctrl + Option |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| Firefox | Alt + Shift | Alt + Shift | Ctrl + Option |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| Safari | Alt | | Ctrl + Option |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
@@ -16,6 +16,7 @@ Use the fields in the *Login* dialog to authenticate your connection:
|
||||
field.
|
||||
* Provide your password in the *Password* field.
|
||||
* Click the *Login* button to securely log into pgAdmin.
|
||||
* Please note that, if the pgAdmin server is restarted then you will be logged out. You need to re-login to continue.
|
||||
|
||||
Recovering a Lost Password
|
||||
**************************
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
.. _master_password:
|
||||
|
||||
************************
|
||||
`Master Password`:index:
|
||||
************************
|
||||
|
||||
A master password is required to secure and later unlock the saved server
|
||||
passwords. This is applicable only for desktop mode users.
|
||||
|
||||
* You are prompted to enter the master password when you open the window for
|
||||
the first time after starting the application.
|
||||
* Once you set the master password, all the existing saved passwords will be
|
||||
re-encrypted using the master password.
|
||||
* The server passwords which are saved in the SQLite DB file are encrypted and
|
||||
decrypted using the master password.
|
||||
|
||||
.. image:: images/master_password_set.png
|
||||
:alt: Set master password
|
||||
:align: center
|
||||
|
||||
* You can disable the master password by setting the configuration parameter
|
||||
*MASTER_PASSWORD_REQUIRED=False*. See :ref:`Desktop Deployment <desktop_deployment>`
|
||||
for more information.
|
||||
|
||||
.. note:: If the master password is disabled, then all the saved passwords will
|
||||
be removed.
|
||||
|
||||
.. warning:: If the master password is disabled, then the saved passwords will
|
||||
be encrypted using a key which is derived from information within the
|
||||
configuration database. Use of a master password ensures that the encryption
|
||||
key does not need to be stored anywhere, and thus prevents possible access
|
||||
to server credentials if the configuration database becomes available to an
|
||||
attacker.
|
||||
|
||||
It is **strongly** recommended that you use the master password if you use
|
||||
the *Save Password* option.
|
||||
|
||||
* The master password is not stored anywhere on the physical storage. It is
|
||||
temporarily stored in the application memory and it does not get saved when
|
||||
the application is restarted.
|
||||
* You are prompted to enter the master password when pgAdmin server is
|
||||
restarted.
|
||||
|
||||
.. image:: images/master_password_enter.png
|
||||
:alt: Enter master password
|
||||
:align: center
|
||||
|
||||
|
||||
* If you forget the master password, you can use the *Reset Master Password*
|
||||
button to reset the password.
|
||||
|
||||
.. image:: images/master_password_reset.png
|
||||
:alt: Reset master password
|
||||
:align: center
|
||||
|
||||
.. warning:: Resetting the master password will also remove all saved passwords
|
||||
and close all existing established connections.
|
||||
@@ -25,6 +25,9 @@ Use the *File* menu to access the following options:
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| *Reset Layout* | If you have modified the workspace, click to restore the default layout. |
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| *Lock Layout* | Click to open a submenu to select the level for locking the UI layout |
|
||||
| | This can also be changed from browser display :ref:`preferences <preferences>` |
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
The Object Menu
|
||||
***************
|
||||
|
||||
@@ -38,10 +38,6 @@ Use the fields on the *Display* panel to specify general display preferences:
|
||||
attempt to catch browser close or refresh events and prompt before allowing
|
||||
them to continue.
|
||||
|
||||
* When the *Show system objects?* switch is set to *True*, the client will
|
||||
display system objects such as system schemas (for example, *pg_temp*) or
|
||||
system columns (for example, *xmin* or *ctid*) in the tree control.
|
||||
|
||||
* When the *Enable browser tree animation?* switch is set to *True*, the client
|
||||
will display the animated tree control otherwise it will be unanimated.
|
||||
|
||||
@@ -49,6 +45,23 @@ Use the fields on the *Display* panel to specify general display preferences:
|
||||
the client will display the animated dialogues/notifications otherwise it
|
||||
will be unanimated.
|
||||
|
||||
* Use the *Lock layout* field to lock the UI layout at different levels. This
|
||||
can also be changed from File menu on the :ref:`menu bar <menu_bar>`
|
||||
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| Option | Action |
|
||||
+=====================+===================================================================+
|
||||
| *None* | No locking. Every panel is resizable and dockable. |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| *Prevent docking* | This will disable the docking/undocking of the panels |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| *Full* | This will disable resizing, docking/undocking of the panels |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
|
||||
* When the *Show system objects?* switch is set to *True*, the client will
|
||||
display system objects such as system schemas (for example, *pg_temp*) or
|
||||
system columns (for example, *xmin* or *ctid*) in the tree control.
|
||||
|
||||
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
||||
main window navigation:
|
||||
|
||||
|
||||
@@ -96,6 +96,11 @@ and commenting:
|
||||
* Implement or remove SQL style or toggle C style comment notation within your
|
||||
code.
|
||||
|
||||
You can also **drag and drop** certain objects from the treeview which
|
||||
can save time in typing long object names. Text containing the object name will be
|
||||
fully qualified with schema. Double quotes will be added if required.
|
||||
For functions and procedures, the function name along with parameter names will
|
||||
be pasted in the Query Tool.
|
||||
|
||||
The Data Output Panel
|
||||
*********************
|
||||
|
||||
@@ -11,6 +11,10 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_4_10
|
||||
release_notes_4_9
|
||||
release_notes_4_8
|
||||
release_notes_4_7
|
||||
release_notes_4_6
|
||||
release_notes_4_5
|
||||
release_notes_4_4
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
************
|
||||
Version 4.10
|
||||
************
|
||||
|
||||
Release date: 2019-07-04
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.9.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Feature #4139 <https://redmine.postgresql.org/issues/4139>`_ - Allow some objects to be dragged/dropped into the Query Tool to insert their signature into the query text.
|
||||
| `Feature #4400 <https://redmine.postgresql.org/issues/4400>`_ - Allow the path to /pgadmin4/servers.json to be overridden in the container distribution.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #4403 <https://redmine.postgresql.org/issues/4403>`_ - Ensure the browser close confirmation is only shown when closing a Query Tool which is running in a separate browser tab.
|
||||
| `Bug #4404 <https://redmine.postgresql.org/issues/4404>`_ - Prevent an error that may occur when editing data with an integer primary key.
|
||||
| `Bug #4407 <https://redmine.postgresql.org/issues/4407>`_ - Fix a quoting issue that caused a blank UI to be displayed when running in French.
|
||||
| `Bug #4421 <https://redmine.postgresql.org/issues/4421>`_ - Ensure the version comparision should be correct for windows installer.
|
||||
@@ -0,0 +1,34 @@
|
||||
***********
|
||||
Version 4.7
|
||||
***********
|
||||
|
||||
Release date: 2019-05-30
|
||||
|
||||
This release contains a number of bug fixes since the release of pgAdmin4 4.6.
|
||||
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #3377 <https://redmine.postgresql.org/issues/3377>`_ - In server mode, update all the saved server credentials when user password is changed.
|
||||
| `Bug #3885 <https://redmine.postgresql.org/issues/3885>`_ - Fix the responsive layout of the main menu bar.
|
||||
| `Bug #4162 <https://redmine.postgresql.org/issues/4162>`_ - Fix syntax error when adding more than one column to the existing table.
|
||||
| `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 #4184 <https://redmine.postgresql.org/issues/4184>`_ - Added Master Password to increase the security of saved passwords.
|
||||
| `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, per Alvin Lindstam
|
||||
| `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.
|
||||
| `Bug #4244 <https://redmine.postgresql.org/issues/4244>`_ - Fix Tab key issue for Toggle switch controls and button on the dialog footer in Safari browser.
|
||||
| `Bug #4245 <https://redmine.postgresql.org/issues/4245>`_ - Ensure that element should get highlighted when they get focus on using Tab key.
|
||||
| `Bug #4246 <https://redmine.postgresql.org/issues/4246>`_ - Fixed console error when subnode control is used in panels.
|
||||
| `Bug #4261 <https://redmine.postgresql.org/issues/4261>`_ - Stop using application/x-javascript as a mime type and use the RFC-compliant application/javascript instead.
|
||||
| `Bug #4262 <https://redmine.postgresql.org/issues/4262>`_ - Fixed error on displaying table properties of a table partitioned by list having a default partition.
|
||||
| `Bug #4263 <https://redmine.postgresql.org/issues/4263>`_ - Fix handling of JSON in the Query Tool with NULL elements.
|
||||
| `Bug #4269 <https://redmine.postgresql.org/issues/4269>`_ - Fix navigation of switch cells in grids.
|
||||
| `Bug #4275 <https://redmine.postgresql.org/issues/4275>`_ - Clarify wording for the NO INHERIT option on constraints, per Michel Feinstein.
|
||||
| `Bug #4276 <https://redmine.postgresql.org/issues/4276>`_ - Relax the permission check on the directory containing the config database, as it may fail in some environments such as OpenShift.
|
||||
| `Bug #4278 <https://redmine.postgresql.org/issues/4278>`_ - Prevent Backgrid Password cells from losing focus if the browser opens an autocomplete list.
|
||||
| `Bug #4284 <https://redmine.postgresql.org/issues/4284>`_ - Fix syntax error when creating a table with a serial column.
|
||||
@@ -0,0 +1,19 @@
|
||||
***********
|
||||
Version 4.8
|
||||
***********
|
||||
|
||||
Release date: 2019-06-03
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.7.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Feature #2653 <https://redmine.postgresql.org/issues/2653>`_ - Allow the UI layout to be fully locked or to prevent docking changes.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #4169 <https://redmine.postgresql.org/issues/4169>`_ - Omit the geometry viewer in the Query Tool from layout saving.
|
||||
| `Bug #4307 <https://redmine.postgresql.org/issues/4307>`_ - Improve the performance of explain plan by embedding the images only when downloading it.
|
||||
| `Bug #4308 <https://redmine.postgresql.org/issues/4308>`_ - Fix the issue of accessing the SQL for Views and Materialized Views. Regression of pluralisation of folder names.
|
||||
@@ -0,0 +1,44 @@
|
||||
***********
|
||||
Version 4.9
|
||||
***********
|
||||
|
||||
Release date: 2019-06-27
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.8.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Feature #3174 <https://redmine.postgresql.org/issues/3174>`_ - Visually distinguish simple tables from tables that are inherited and from which other tables are inherited.
|
||||
| `Feature #4202 <https://redmine.postgresql.org/issues/4202>`_ - Add a framework for testing reversed engineered SQL and CRUD API endpoints.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #3994 <https://redmine.postgresql.org/issues/3994>`_ - Fix issue where the dependencies tab for inherited tables/foreign keys shows partial text.
|
||||
| `Bug #4036 <https://redmine.postgresql.org/issues/4036>`_ - Allow editing of data where a primary key column includes a % sign in the value.
|
||||
| `Bug #4171 <https://redmine.postgresql.org/issues/4171>`_ - Fix issue where reverse engineered SQL was failing for foreign tables, if it had "=" in the options.
|
||||
| `Bug #4195 <https://redmine.postgresql.org/issues/4195>`_ - Fix keyboard navigation in "inner" tabsets such as the Query Tool and Debugger.
|
||||
| `Bug #4228 <https://redmine.postgresql.org/issues/4228>`_ - Ensure the correct label is used in panel headers when viewing filtered rows.
|
||||
| `Bug #4253 <https://redmine.postgresql.org/issues/4253>`_ - Fix issue where new column should be created with Default value.
|
||||
| `Bug #4283 <https://redmine.postgresql.org/issues/4283>`_ - Initial support for PostgreSQL 12.
|
||||
| `Bug #4288 <https://redmine.postgresql.org/issues/4288>`_ - Initial support for PostgreSQL 12.
|
||||
| `Bug #4290 <https://redmine.postgresql.org/issues/4290>`_ - Initial support for PostgreSQL 12.
|
||||
| `Bug #4255 <https://redmine.postgresql.org/issues/4255>`_ - Prevent the geometry viewer grabbing key presses when not in focus under Firefox, IE and Edge.
|
||||
| `Bug #4306 <https://redmine.postgresql.org/issues/4306>`_ - Prevent the "Please login to access this page" message displaying multiple times.
|
||||
| `Bug #4310 <https://redmine.postgresql.org/issues/4310>`_ - Ensure that the Return key can be used to submit the Master Password dialogue.
|
||||
| `Bug #4317 <https://redmine.postgresql.org/issues/4317>`_ - Ensure that browser auto-fill doesn't cause Help pages to be opened unexpectedly.
|
||||
| `Bug #4320 <https://redmine.postgresql.org/issues/4320>`_ - Fix issue where SSH tunnel connection using password is failing, it's regression of Master Password.
|
||||
| `Bug #4329 <https://redmine.postgresql.org/issues/4329>`_ - Fix an initialisation error when two functions with parameters are debugged in parallel.
|
||||
| `Bug #4343 <https://redmine.postgresql.org/issues/4343>`_ - Fix issue where property dialog of column should open properly for EPAS v12.
|
||||
| `Bug #4345 <https://redmine.postgresql.org/issues/4345>`_ - Capitalize the word 'export' used in Import/Export module.
|
||||
| `Bug #4349 <https://redmine.postgresql.org/issues/4349>`_ - Ensure strings are properly encoded in the Query History.
|
||||
| `Bug #4350 <https://redmine.postgresql.org/issues/4350>`_ - Ensure we include the CSRF token when uploading files.
|
||||
| `Bug #4357 <https://redmine.postgresql.org/issues/4357>`_ - Fix connection restoration issue when pgAdmin server is restarted and the page is refreshed.
|
||||
| `Bug #4360 <https://redmine.postgresql.org/issues/4360>`_ - Ensure the debugger control buttons are only enabled once initialisation is complete.
|
||||
| `Bug #4362 <https://redmine.postgresql.org/issues/4362>`_ - Remove additional "SETOF" included when generating CREATE scripts for trigger functions.
|
||||
| `Bug #4365 <https://redmine.postgresql.org/issues/4365>`_ - Fix help links for backup globals and backup server.
|
||||
| `Bug #4367 <https://redmine.postgresql.org/issues/4367>`_ - Fix an XSS issue seen in View/Edit data mode if a column name includes HTML.
|
||||
| `Bug #4378 <https://redmine.postgresql.org/issues/4378>`_ - Ensure Python escaping matched JS escaping and fix a minor XSS issue in the Query Tool that required superuser access to trigger.
|
||||
| `Bug #4380 <https://redmine.postgresql.org/issues/4380>`_ - Ensure that both columns and partitions can be edited at the same time in the table dialog.
|
||||
| `Bug #4386 <https://redmine.postgresql.org/issues/4386>`_ - Fix an XSS issue when username contains XSS vulnerable text.
|
||||
@@ -68,11 +68,15 @@ pgAdmin. These are as follows:
|
||||
|
||||
* ``config_distro.py``: This file is read after ``config.py`` and is intended for
|
||||
packagers to change any settings that are required for their pgAdmin distribution.
|
||||
This may typically include certain paths and file locations.
|
||||
This may typically include certain paths and file locations. This file is optional,
|
||||
and may be created by packagers in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
* ``config_local.py``: This file is read after ``config_distro.py`` and is intended
|
||||
for end users to change any default or packaging specific settings that they may
|
||||
wish to adjust to meet local preferences or standards.
|
||||
wish to adjust to meet local preferences or standards. This file is optional,
|
||||
and may be created by administrators in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
Python
|
||||
------
|
||||
|
||||
@@ -264,8 +264,9 @@ Use the fields in the *Definition* tab to define the check constraint:
|
||||
|
||||
* Provide the expression that a row must satisfy in the *Check* field. This
|
||||
field is required.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify this constraint
|
||||
is automatically inherited by a table's children. The default is *No*.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify that this
|
||||
constraint is not automatically inherited by a table's children. The default
|
||||
is *No*, meaning that the constraint will be inherited by any children.
|
||||
* Move the *Don't validate?* switch to the *No* position to skip validation of
|
||||
existing data; the constraint may not hold for all rows in the table. The
|
||||
default is *Yes*.
|
||||
|
||||
@@ -19,6 +19,12 @@ view:
|
||||
control.
|
||||
* Click the minus sign (-) to the left of a node to close that node.
|
||||
|
||||
You can also **drag and drop** certain objects to the Query Tool which
|
||||
can save time in typing long object names. Text containing the object name will be
|
||||
fully qualified with schema. Double quotes will be added if required.
|
||||
For functions and procedures, the function name along with parameter names will
|
||||
be pasted in the Query Tool.
|
||||
|
||||
Access context-sensitive menus by right-clicking on a node of the tree control
|
||||
to perform common tasks. Menus display options that include one or more of the
|
||||
following selections (options appear in alphabetical order):
|
||||
|
||||
@@ -22,8 +22,8 @@ RUN yarn install --cache-folder ./ycache --verbose && \
|
||||
FROM python:3.7-alpine3.9 as docs-builder
|
||||
|
||||
# Install only dependencies absolutely required for documentation building
|
||||
RUN apk add --no-cache make && \
|
||||
pip install --no-cache-dir sphinx flask_security flask_paranoid python-dateutil flask_sqlalchemy flask_gravatar flask_migrate simplejson
|
||||
RUN apk add --no-cache make build-base openssl-dev libffi-dev && \
|
||||
pip install --no-cache-dir sphinx flask_security flask_paranoid python-dateutil flask_sqlalchemy flask_gravatar flask_migrate simplejson cryptography
|
||||
|
||||
COPY ./pgadmin4/ /pgadmin4
|
||||
|
||||
|
||||
@@ -15,9 +15,10 @@ if [ ! -f /var/lib/pgadmin/pgadmin4.db ]; then
|
||||
# Importing pgadmin4 (from this script) is enough
|
||||
python run_pgadmin.py
|
||||
|
||||
export PGADMIN_SERVER_JSON_FILE=${PGADMIN_SERVER_JSON:-/pgadmin4/servers.json}
|
||||
# Pre-load any required servers
|
||||
if [ -f /pgadmin4/servers.json ]; then
|
||||
/usr/local/bin/python /pgadmin4/setup.py --load-servers /pgadmin4/servers.json --user ${PGADMIN_DEFAULT_EMAIL}
|
||||
if [ -f "${PGADMIN_SERVER_JSON_FILE}" ]; then
|
||||
/usr/local/bin/python /pgadmin4/setup.py --load-servers "${PGADMIN_SERVER_JSON_FILE}" --user ${PGADMIN_DEFAULT_EMAIL}
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -64,6 +64,82 @@ Root: HKLM; Subkey: "Software\{#MyAppName}\{#MyAppVersion}"; ValueType: string;
|
||||
var
|
||||
UpgradeMode: Boolean;
|
||||
|
||||
// Procedure to split a string into an array of integers
|
||||
procedure Explode(var Dest: TArrayOfInteger; Text: String; Separator: String);
|
||||
var
|
||||
i, p: Integer;
|
||||
begin
|
||||
i := 0;
|
||||
repeat
|
||||
SetArrayLength(Dest, i+1);
|
||||
p := Pos(Separator,Text);
|
||||
if p > 0 then begin
|
||||
Dest[i] := StrToInt(Copy(Text, 1, p-1));
|
||||
Text := Copy(Text, p + Length(Separator), Length(Text));
|
||||
i := i + 1;
|
||||
end else begin
|
||||
Dest[i] := StrToInt(Text);
|
||||
Text := '';
|
||||
end;
|
||||
until Length(Text)=0;
|
||||
end;
|
||||
|
||||
// Function compares version strings numerically:
|
||||
// * when v1 = v2, result = 0
|
||||
// * when v1 < v2, result = -1
|
||||
// * when v1 > v2, result = 1
|
||||
//
|
||||
// Supports version numbers with trailing zeroes, for example 1.02.05.
|
||||
// Supports comparison of two version number of different lengths,
|
||||
// for example CompareVersions('1.2', '2.0.3')
|
||||
// When any of the parameters is '' (empty string) it considers version
|
||||
// number as 0
|
||||
function CompareVersions(v1: String; v2: String): Integer;
|
||||
var
|
||||
v1parts: TArrayOfInteger;
|
||||
v2parts: TArrayOfInteger;
|
||||
i: Integer;
|
||||
begin
|
||||
if v1 = '' then
|
||||
begin
|
||||
v1 := '0';
|
||||
end;
|
||||
|
||||
if v2 = '' then
|
||||
begin
|
||||
v2 := '0';
|
||||
end;
|
||||
|
||||
Explode(v1parts, v1, '.');
|
||||
Explode(v2parts, v2, '.');
|
||||
|
||||
if (GetArrayLength(v1parts) > GetArrayLength(v2parts)) then
|
||||
begin
|
||||
SetArrayLength(v2parts, GetArrayLength(v1parts))
|
||||
end else if (GetArrayLength(v2parts) > GetArrayLength(v1parts)) then
|
||||
begin
|
||||
SetArrayLength(v1parts, GetArrayLength(v2parts))
|
||||
end;
|
||||
|
||||
for i := 0 to GetArrayLength(v1parts) - 1 do
|
||||
begin
|
||||
if v1parts[i] > v2parts[i] then
|
||||
begin
|
||||
{ v1 is greater }
|
||||
Result := 1;
|
||||
exit;
|
||||
end else if v1parts[i] < v2parts[i] then
|
||||
begin
|
||||
{ v2 is greater }
|
||||
Result := -1;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Are Equal }
|
||||
Result := 0;
|
||||
end;
|
||||
|
||||
function IsPathValid(Path: string): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
@@ -103,7 +179,7 @@ begin
|
||||
begin
|
||||
UpgradeMode := True;
|
||||
RegQueryStringValue(HKEY_LOCAL_MACHINE,'Software\{#MyAppName}\{#MyAppVersion}', 'Version', Version);
|
||||
if Version > '{#MyAppFullVersion}' then
|
||||
if CompareVersions(Version, '{#MyAppFullVersion}') = 1 then
|
||||
begin
|
||||
MsgBox(ExpandConstant('{cm:NewerVersionExists}' + '(v' + Version + ') is already installed' ), mbInformation, MB_OK);
|
||||
Result := False;
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<key>CFBundleName</key>
|
||||
<string>pgAdmin 4</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.6.0</string>
|
||||
<string>4.10.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4.6.0</string>
|
||||
<string>4.10.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION = 4.6.0.0
|
||||
VERSION = 4.10.0.0
|
||||
QMAKE_TARGET_COMPANY = "The pgAdmin Development Team"
|
||||
QMAKE_TARGET_PRODUCT = "pgAdmin 4"
|
||||
QMAKE_TARGET_DESCRIPTION = "pgAdmin 4 Desktop Runtime"
|
||||
|
||||
@@ -50,7 +50,7 @@ APP_ICON = 'pg-icon'
|
||||
|
||||
# Application version number components
|
||||
APP_RELEASE = 4
|
||||
APP_REVISION = 6
|
||||
APP_REVISION = 10
|
||||
|
||||
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
|
||||
# for GA releases.
|
||||
@@ -59,7 +59,7 @@ APP_SUFFIX = ''
|
||||
# Numeric application version for upgrade checks. Should be in the format:
|
||||
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
|
||||
# zero if needed, and Z represents the suffix, with a leading zero if needed
|
||||
APP_VERSION_INT = 40600
|
||||
APP_VERSION_INT = 41000
|
||||
|
||||
# DO NOT CHANGE!
|
||||
# The application version string, constructed from the components
|
||||
@@ -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).
|
||||
@@ -418,6 +419,12 @@ SUPPORT_SSH_TUNNEL = True
|
||||
# Set to False to disable password saving.
|
||||
ALLOW_SAVE_TUNNEL_PASSWORD = False
|
||||
|
||||
##########################################################################
|
||||
# Master password is used to encrypt/decrypt saved server passwords
|
||||
# Applicable for desktop mode only
|
||||
##########################################################################
|
||||
MASTER_PASSWORD_REQUIRED = True
|
||||
|
||||
##########################################################################
|
||||
# Local config settings
|
||||
##########################################################################
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
"""empty message
|
||||
|
||||
Revision ID: 35f29b1701bd
|
||||
Revises: ec1cac3399c9
|
||||
Create Date: 2019-04-26 16:38:08.368471
|
||||
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pgadmin.model import db, Server
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '35f29b1701bd'
|
||||
down_revision = 'ec1cac3399c9'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
db.engine.execute("ALTER TABLE user RENAME TO user_old")
|
||||
|
||||
db.engine.execute("""
|
||||
CREATE TABLE user (
|
||||
id INTEGER NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
password VARCHAR(256),
|
||||
active BOOLEAN NOT NULL,
|
||||
confirmed_at DATETIME,
|
||||
masterpass_check VARCHAR(256),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (email),
|
||||
CHECK (active IN (0, 1))
|
||||
);
|
||||
""")
|
||||
|
||||
db.engine.execute("""
|
||||
INSERT INTO user (
|
||||
id, email, password, active, confirmed_at
|
||||
) SELECT
|
||||
id, email, password, active, confirmed_at
|
||||
FROM user_old""")
|
||||
|
||||
db.engine.execute("DROP TABLE user_old")
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
||||
@@ -31,7 +31,10 @@ def upgrade():
|
||||
Security(app, user_datastore, register_blueprint=False)
|
||||
else:
|
||||
app.config['SECURITY_PASSWORD_SALT'] = current_salt
|
||||
users = User.query.all()
|
||||
|
||||
users = User.query.with_entities(
|
||||
User.id, User.email, User.password, User.active, User.confirmed_at)\
|
||||
.all()
|
||||
# This will upgrade the plaintext password of all the user as per the
|
||||
# SECURITY_PASSWORD_HASH.
|
||||
for user in users:
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"bootstrap4-toggle": "3.4.0",
|
||||
"bowser": "2.1.2",
|
||||
"browserify": "~16.2.3",
|
||||
"codemirror": "^5.44.0",
|
||||
"codemirror": "^5.47.0",
|
||||
"css-loader": "2.1.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"dropzone": "^5.5.1",
|
||||
@@ -67,11 +67,11 @@
|
||||
"immutability-helper": "^3.0.0",
|
||||
"imports-loader": "^0.8.0",
|
||||
"ip-address": "^5.8.9",
|
||||
"jquery": "3.3.1",
|
||||
"jquery": "^3.4.0",
|
||||
"jquery-contextmenu": "^2.8.0",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"json-bignumber": "^1.0.1",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet": "^1.5.1",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.23",
|
||||
"mousetrap": "^1.6.3",
|
||||
@@ -89,7 +89,7 @@
|
||||
"underscore": "^1.9.1",
|
||||
"underscore.string": "^3.3.5",
|
||||
"watchify": "~3.11.1",
|
||||
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker/#e191a1063c111d5542ff26a7c163940be180333d",
|
||||
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker/#f3c79e4b9a9e76a8e34736fc33473e65c524050c",
|
||||
"wkx": "^0.4.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -23,20 +23,21 @@ from flask_login import user_logged_in, user_logged_out
|
||||
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 flask_security.utils import login_user, logout_user
|
||||
from werkzeug.datastructures import ImmutableDict
|
||||
from werkzeug.local import LocalProxy
|
||||
from werkzeug.utils import find_modules
|
||||
|
||||
from pgadmin.model import db, Role, Server, ServerGroup, \
|
||||
User, Keys, Version, SCHEMA_VERSION as CURRENT_SCHEMA_VERSION
|
||||
from pgadmin.utils import PgAdminModule, driver
|
||||
from pgadmin.utils import PgAdminModule, driver, KeyManager
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.utils.session import create_session_interface, pga_unauthorised
|
||||
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
|
||||
@@ -235,6 +236,8 @@ def create_app(app_name=None):
|
||||
os.environ['PGADMIN_TESTING_MODE'] == '1'
|
||||
):
|
||||
config.SQLITE_PATH = config.TEST_SQLITE_PATH
|
||||
config.MASTER_PASSWORD_REQUIRED = False
|
||||
config.UPGRADE_CHECK_ENABLED = False
|
||||
|
||||
# Ensure the various working directories exist
|
||||
from pgadmin.setup import create_app_data_directory, db_upgrade
|
||||
@@ -338,7 +341,8 @@ def create_app(app_name=None):
|
||||
set_version(CURRENT_SCHEMA_VERSION)
|
||||
db.session.commit()
|
||||
|
||||
os.chmod(config.SQLITE_PATH, 0o600)
|
||||
if os.name != 'nt':
|
||||
os.chmod(config.SQLITE_PATH, 0o600)
|
||||
|
||||
Mail(app)
|
||||
|
||||
@@ -366,7 +370,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)
|
||||
@@ -569,12 +576,23 @@ def create_app(app_name=None):
|
||||
def force_session_write(app, user):
|
||||
session.force_write = True
|
||||
|
||||
@user_logged_in.connect_via(app)
|
||||
def store_crypt_key(app, user):
|
||||
# in desktop mode, master password is used to encrypt/decrypt
|
||||
# and is stored in the keyManager memory
|
||||
if config.SERVER_MODE:
|
||||
if 'password' in request.form:
|
||||
current_app.keyManager.set(request.form['password'])
|
||||
|
||||
@user_logged_out.connect_via(app)
|
||||
def current_user_cleanup(app, user):
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from flask import current_app
|
||||
|
||||
# remove key
|
||||
current_app.keyManager.reset()
|
||||
|
||||
for mdl in current_app.logout_hooks:
|
||||
try:
|
||||
mdl.on_logout(user)
|
||||
@@ -625,6 +643,15 @@ def create_app(app_name=None):
|
||||
abort(401)
|
||||
login_user(user)
|
||||
|
||||
# if the server is restarted the in memory key will be lost
|
||||
# but the user session may still be active. Logout the user
|
||||
# to get the key again when login
|
||||
if config.SERVER_MODE and current_user.is_authenticated:
|
||||
if current_app.keyManager.get() is None and \
|
||||
request.endpoint not in ('security.login',
|
||||
'security.logout'):
|
||||
logout_user()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
if 'key' in request.args:
|
||||
@@ -705,8 +732,16 @@ def create_app(app_name=None):
|
||||
current_app.logger.error(e, exc_info=True)
|
||||
return e
|
||||
|
||||
# Intialize the key manager
|
||||
app.keyManager = KeyManager()
|
||||
|
||||
##########################################################################
|
||||
# Protection against CSRF attacks
|
||||
##########################################################################
|
||||
with app.app_context():
|
||||
pgCSRFProtect.init_app(app)
|
||||
|
||||
##########################################################################
|
||||
# All done!
|
||||
##########################################################################
|
||||
|
||||
return app
|
||||
@@ -18,7 +18,7 @@ from socket import error as SOCKETErrorException
|
||||
|
||||
import six
|
||||
from flask import current_app, render_template, url_for, make_response, \
|
||||
flash, Response, request, after_this_request, redirect
|
||||
flash, Response, request, after_this_request, redirect, session
|
||||
from flask_babelex import gettext
|
||||
from flask_gravatar import Gravatar
|
||||
from flask_login import current_user, login_required
|
||||
@@ -37,9 +37,14 @@ 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.utils.menu import MenuItem
|
||||
from pgadmin.browser.register_browser_preferences import \
|
||||
register_browser_preferences
|
||||
from pgadmin.utils.master_password import validate_master_password, \
|
||||
set_masterpass_check_text, cleanup_master_password, get_crypt_key, \
|
||||
set_crypt_key, process_masterpass_disabled
|
||||
|
||||
try:
|
||||
import urllib.request as urlreq
|
||||
@@ -211,6 +216,40 @@ class BrowserModule(PgAdminModule):
|
||||
scripts.extend(module.get_own_javascripts())
|
||||
return scripts
|
||||
|
||||
def get_own_menuitems(self):
|
||||
return {
|
||||
'file_items': [
|
||||
MenuItem(
|
||||
name='mnu_locklayout',
|
||||
module='pgAdmin.Browser',
|
||||
label=gettext('Lock Layout'),
|
||||
priority=999,
|
||||
menu_items=[MenuItem(
|
||||
name='mnu_lock_none',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_none',
|
||||
priority=0,
|
||||
label=gettext('None'),
|
||||
checked=True
|
||||
), MenuItem(
|
||||
name='mnu_lock_docking',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_docking',
|
||||
priority=1,
|
||||
label=gettext('Prevent Docking'),
|
||||
checked=False
|
||||
), MenuItem(
|
||||
name='mnu_lock_full',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_full',
|
||||
priority=2,
|
||||
label=gettext('Full Lock'),
|
||||
checked=False
|
||||
)]
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
def register_preferences(self):
|
||||
register_browser_preferences(self)
|
||||
|
||||
@@ -219,7 +258,11 @@ class BrowserModule(PgAdminModule):
|
||||
Returns:
|
||||
list: a list of url endpoints exposed to the client.
|
||||
"""
|
||||
return ['browser.index', 'browser.nodes']
|
||||
return ['browser.index', 'browser.nodes',
|
||||
'browser.check_master_password',
|
||||
'browser.set_master_password',
|
||||
'browser.reset_master_password',
|
||||
'browser.lock_layout']
|
||||
|
||||
|
||||
blueprint = BrowserModule(MODULE_NAME, __name__)
|
||||
@@ -478,6 +521,7 @@ class BrowserPluginModule(PgAdminModule):
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def index():
|
||||
"""Render and process the main browser window."""
|
||||
@@ -561,6 +605,7 @@ def index():
|
||||
|
||||
|
||||
@blueprint.route("/js/utils.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def utils():
|
||||
layout = get_setting('Browser/Layout', default='')
|
||||
@@ -623,61 +668,37 @@ def utils():
|
||||
pg_libpq_version=pg_libpq_version,
|
||||
support_ssh_tunnel=config.SUPPORT_SSH_TUNNEL
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/endpoints.js")
|
||||
@pgCSRFProtect.exempt
|
||||
def exposed_urls():
|
||||
return make_response(
|
||||
render_template('browser/js/endpoints.js'),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
200, {'Content-Type': 'application/javascript'}
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/js/error.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def error_js():
|
||||
return make_response(
|
||||
render_template('browser/js/error.js', _=gettext),
|
||||
200, {'Content-Type': 'application/x-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/x-javascript'})
|
||||
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/x-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/x-javascript'})
|
||||
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."""
|
||||
@@ -703,6 +724,148 @@ def get_nodes():
|
||||
return make_json_response(data=nodes)
|
||||
|
||||
|
||||
def form_master_password_response(existing=True, present=False, errmsg=None):
|
||||
content_new = (
|
||||
gettext("Set Master Password"),
|
||||
"<br/>".join([
|
||||
gettext("Please set a master password for pgAdmin."),
|
||||
gettext("This will be used to secure and later unlock saved "
|
||||
"passwords and other credentials.")])
|
||||
)
|
||||
content_existing = (
|
||||
gettext("Unlock Saved Passwords"),
|
||||
"<br/>".join([
|
||||
gettext("Please enter your master password."),
|
||||
gettext("This is required to unlock saved passwords and "
|
||||
"reconnect to the database server(s).")])
|
||||
)
|
||||
|
||||
return make_json_response(data={
|
||||
'present': present,
|
||||
'title': content_existing[0] if existing else content_new[0],
|
||||
'content': render_template(
|
||||
'browser/master_password.html',
|
||||
content_text=content_existing[1] if existing else content_new[1],
|
||||
errmsg=errmsg
|
||||
),
|
||||
'reset': existing
|
||||
})
|
||||
|
||||
|
||||
@blueprint.route("/master_password", endpoint="check_master_password",
|
||||
methods=["GET"])
|
||||
def check_master_password():
|
||||
"""
|
||||
Checks if the master password is available in the memory
|
||||
This password will be used to encrypt/decrypt saved server passwords
|
||||
"""
|
||||
return make_json_response(data=get_crypt_key()[0])
|
||||
|
||||
|
||||
@blueprint.route("/master_password", endpoint="reset_master_password",
|
||||
methods=["DELETE"])
|
||||
def reset_master_password():
|
||||
"""
|
||||
Removes the master password and remove all saved passwords
|
||||
This password will be used to encrypt/decrypt saved server passwords
|
||||
"""
|
||||
cleanup_master_password()
|
||||
return make_json_response(data=get_crypt_key()[0])
|
||||
|
||||
|
||||
@blueprint.route("/master_password", endpoint="set_master_password",
|
||||
methods=["POST"])
|
||||
def set_master_password():
|
||||
"""
|
||||
Set the master password and store in the memory
|
||||
This password will be used to encrypt/decrypt saved server passwords
|
||||
"""
|
||||
|
||||
data = None
|
||||
|
||||
if hasattr(request.data, 'decode'):
|
||||
data = request.data.decode('utf-8')
|
||||
|
||||
if data != '':
|
||||
data = json.loads(data)
|
||||
|
||||
# Master password is not applicable for server mode
|
||||
if not config.SERVER_MODE and config.MASTER_PASSWORD_REQUIRED:
|
||||
|
||||
if data != '' and data.get('password', '') != '':
|
||||
# if master pass is set previously
|
||||
if current_user.masterpass_check is not None:
|
||||
if not validate_master_password(data.get('password')):
|
||||
return form_master_password_response(
|
||||
existing=True,
|
||||
present=False,
|
||||
errmsg=gettext("Incorrect master password")
|
||||
)
|
||||
|
||||
# store the master pass in the memory
|
||||
set_crypt_key(data.get('password'))
|
||||
|
||||
if current_user.masterpass_check is None:
|
||||
# master check is not set, which means the server password
|
||||
# data is old and is encrypted with old key
|
||||
# Re-encrypt with new key
|
||||
|
||||
from pgadmin.browser.server_groups.servers.utils \
|
||||
import reencrpyt_server_passwords
|
||||
reencrpyt_server_passwords(
|
||||
current_user.id, current_user.password,
|
||||
data.get('password'))
|
||||
|
||||
# set the encrypted sample text with the new
|
||||
# master pass
|
||||
set_masterpass_check_text(data.get('password'))
|
||||
|
||||
elif not get_crypt_key()[0] and \
|
||||
current_user.masterpass_check is not None:
|
||||
return form_master_password_response(
|
||||
existing=True,
|
||||
present=False,
|
||||
)
|
||||
elif not get_crypt_key()[0]:
|
||||
return form_master_password_response(
|
||||
existing=False,
|
||||
present=False,
|
||||
)
|
||||
|
||||
# if master password is disabled now, but was used once then
|
||||
# remove all the saved passwords
|
||||
process_masterpass_disabled()
|
||||
|
||||
if config.SERVER_MODE and current_user.masterpass_check is None:
|
||||
|
||||
crypt_key = get_crypt_key()[1]
|
||||
from pgadmin.browser.server_groups.servers.utils \
|
||||
import reencrpyt_server_passwords
|
||||
reencrpyt_server_passwords(
|
||||
current_user.id, current_user.password, crypt_key)
|
||||
|
||||
set_masterpass_check_text(crypt_key)
|
||||
|
||||
return form_master_password_response(
|
||||
present=True,
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/lock_layout", endpoint="lock_layout", methods=["PUT"])
|
||||
def lock_layout():
|
||||
data = None
|
||||
|
||||
if hasattr(request.data, 'decode'):
|
||||
data = request.data.decode('utf-8')
|
||||
|
||||
if data != '':
|
||||
data = json.loads(data)
|
||||
|
||||
blueprint.lock_layout.set(data['value'])
|
||||
|
||||
return make_json_response()
|
||||
|
||||
|
||||
# Only register route if SECURITY_CHANGEABLE is set to True
|
||||
# We can't access app context here so cannot
|
||||
# use app.config['SECURITY_CHANGEABLE']
|
||||
@@ -711,6 +874,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."""
|
||||
@@ -760,6 +924,15 @@ if hasattr(config, 'SECURITY_CHANGEABLE') and config.SECURITY_CHANGEABLE:
|
||||
if request.json is None and not has_error:
|
||||
after_this_request(_commit)
|
||||
do_flash(*get_message('PASSWORD_CHANGE'))
|
||||
|
||||
old_key = get_crypt_key()[1]
|
||||
set_crypt_key(form.new_password.data, False)
|
||||
|
||||
from pgadmin.browser.server_groups.servers.utils \
|
||||
import reencrpyt_server_passwords
|
||||
reencrpyt_server_passwords(
|
||||
current_user.id, old_key, form.new_password.data)
|
||||
|
||||
return redirect(get_url(_security.post_change_view) or
|
||||
get_url(_security.post_login_view))
|
||||
|
||||
@@ -794,6 +967,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 +1034,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:
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
##########################################################################
|
||||
from flask_babelex import gettext
|
||||
|
||||
LOCK_LAYOUT_LEVEL = {
|
||||
'PREVENT_DOCKING': 'docking',
|
||||
'FULL': 'full',
|
||||
'NONE': 'none'
|
||||
}
|
||||
|
||||
|
||||
def register_browser_preferences(self):
|
||||
self.show_system_objects = self.preference.register(
|
||||
@@ -58,6 +64,21 @@ def register_browser_preferences(self):
|
||||
)
|
||||
)
|
||||
|
||||
self.lock_layout = self.preference.register(
|
||||
'display', 'lock_layout',
|
||||
gettext('Lock Layout'), 'radioModern', LOCK_LAYOUT_LEVEL['NONE'],
|
||||
category_label=gettext('Display'), options=[
|
||||
{'label': gettext('None'), 'value': LOCK_LAYOUT_LEVEL['NONE']},
|
||||
{'label': gettext('Prevent Docking'),
|
||||
'value': LOCK_LAYOUT_LEVEL['PREVENT_DOCKING']},
|
||||
{'label': gettext('Full Lock'),
|
||||
'value': LOCK_LAYOUT_LEVEL['FULL']},
|
||||
],
|
||||
help_str=gettext(
|
||||
'Lock the UI layout at different levels'
|
||||
)
|
||||
)
|
||||
|
||||
self.table_row_count_threshold = self.preference.register(
|
||||
'properties', 'table_row_count_threshold',
|
||||
gettext("Count rows if estimated less than"), 'integer', 2000,
|
||||
|
||||
@@ -26,6 +26,8 @@ import config
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.model import db, Server, ServerGroup, User
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.utils.master_password import get_crypt_key
|
||||
from pgadmin.utils.exception import CryptKeyMissing
|
||||
|
||||
|
||||
def has_any(data, keys):
|
||||
@@ -117,9 +119,16 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
|
||||
for server in servers:
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
connected = False
|
||||
manager = None
|
||||
try:
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
except CryptKeyMissing:
|
||||
# show the nodes at least even if not able to connect.
|
||||
pass
|
||||
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
|
||||
@@ -723,6 +732,11 @@ class ServerNode(PGChildNodeView):
|
||||
request.data, encoding='utf-8'
|
||||
)
|
||||
|
||||
# Get enc key
|
||||
crypt_key_present, crypt_key = get_crypt_key()
|
||||
if not crypt_key_present:
|
||||
raise CryptKeyMissing
|
||||
|
||||
# Some fields can be provided with service file so they are optional
|
||||
if 'service' in data and not data['service']:
|
||||
required_args.extend([
|
||||
@@ -807,7 +821,7 @@ class ServerNode(PGChildNodeView):
|
||||
# login with password
|
||||
have_password = True
|
||||
password = data['password']
|
||||
password = encrypt(password, current_user.password)
|
||||
password = encrypt(password, crypt_key)
|
||||
elif 'passfile' in data and data["passfile"] != '':
|
||||
passfile = data['passfile']
|
||||
setattr(server, 'passfile', passfile)
|
||||
@@ -817,7 +831,7 @@ class ServerNode(PGChildNodeView):
|
||||
have_tunnel_password = True
|
||||
tunnel_password = data['tunnel_password']
|
||||
tunnel_password = \
|
||||
encrypt(tunnel_password, current_user.password)
|
||||
encrypt(tunnel_password, crypt_key)
|
||||
|
||||
status, errmsg = conn.connect(
|
||||
password=password,
|
||||
@@ -930,7 +944,7 @@ class ServerNode(PGChildNodeView):
|
||||
"servers/supported_servers.js",
|
||||
server_types=ServerType.types()
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
200, {'Content-Type': 'application/javascript'}
|
||||
)
|
||||
|
||||
def connect_status(self, gid, sid):
|
||||
@@ -998,6 +1012,11 @@ class ServerNode(PGChildNodeView):
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
|
||||
# Get enc key
|
||||
crypt_key_present, crypt_key = get_crypt_key()
|
||||
if not crypt_key_present:
|
||||
raise CryptKeyMissing
|
||||
|
||||
# If server using SSH Tunnel
|
||||
if server.use_ssh_tunnel:
|
||||
if 'tunnel_password' not in data:
|
||||
@@ -1014,12 +1033,12 @@ class ServerNode(PGChildNodeView):
|
||||
# Encrypt the password before saving with user's login
|
||||
# password key.
|
||||
try:
|
||||
tunnel_password = encrypt(tunnel_password, user.password) \
|
||||
tunnel_password = encrypt(tunnel_password, crypt_key) \
|
||||
if tunnel_password is not None else \
|
||||
server.tunnel_password
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return internal_server_error(errormsg=e.message)
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
||||
if 'password' not in data:
|
||||
conn_passwd = getattr(conn, 'password', None)
|
||||
@@ -1038,11 +1057,11 @@ class ServerNode(PGChildNodeView):
|
||||
# Encrypt the password before saving with user's login
|
||||
# password key.
|
||||
try:
|
||||
password = encrypt(password, user.password) \
|
||||
password = encrypt(password, crypt_key) \
|
||||
if password is not None else server.password
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return internal_server_error(errormsg=e.message)
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
||||
# Check do we need to prompt for the database server or ssh tunnel
|
||||
# password or both. Return the password template in case password is
|
||||
@@ -1235,6 +1254,7 @@ class ServerNode(PGChildNodeView):
|
||||
"""
|
||||
try:
|
||||
data = json.loads(request.form['data'], encoding='utf-8')
|
||||
crypt_key = get_crypt_key()[1]
|
||||
|
||||
# Fetch Server Details
|
||||
server = Server.query.filter_by(id=sid).first()
|
||||
@@ -1292,7 +1312,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
# Check against old password only if no pgpass file
|
||||
if not is_passfile:
|
||||
decrypted_password = decrypt(manager.password, user.password)
|
||||
decrypted_password = decrypt(manager.password, crypt_key)
|
||||
|
||||
if isinstance(decrypted_password, bytes):
|
||||
decrypted_password = decrypted_password.decode()
|
||||
@@ -1328,7 +1348,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
# Store password in sqlite only if no pgpass file
|
||||
if not is_passfile:
|
||||
password = encrypt(data['newPassword'], user.password)
|
||||
password = encrypt(data['newPassword'], crypt_key)
|
||||
# Check if old password was stored in pgadmin4 sqlite database.
|
||||
# If yes then update that password.
|
||||
if server.password is not None and config.ALLOW_SAVE_PASSWORD:
|
||||
@@ -1472,6 +1492,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
def get_response_for_password(self, server, status, prompt_password=False,
|
||||
prompt_tunnel_password=False, errmsg=None):
|
||||
|
||||
if server.use_ssh_tunnel:
|
||||
return make_json_response(
|
||||
success=0,
|
||||
@@ -1498,7 +1519,7 @@ class ServerNode(PGChildNodeView):
|
||||
server_label=server.name,
|
||||
username=server.username,
|
||||
errmsg=errmsg,
|
||||
_=gettext
|
||||
_=gettext,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -166,7 +166,6 @@ class DatabaseView(PGChildNodeView):
|
||||
def wrap(f):
|
||||
@wraps(f)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
|
||||
self.manager = get_driver(
|
||||
PG_DEFAULT_DRIVER
|
||||
).connection_manager(
|
||||
|
||||
@@ -208,7 +208,7 @@ class CastView(PGChildNodeView):
|
||||
).connection_manager(kwargs['sid'])
|
||||
self.conn = self.manager.connection(did=kwargs['did'])
|
||||
# Set template path for the SQL scripts
|
||||
self.template_path = 'cast/sql/#{0}#'.format(self.manager.version)
|
||||
self.template_path = 'casts/sql/#{0}#'.format(self.manager.version)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Cast: money -> bigint
|
||||
|
||||
-- DROP CAST (money AS bigint);
|
||||
|
||||
CREATE CAST (money AS bigint)
|
||||
WITHOUT FUNCTION
|
||||
AS IMPLICIT;
|
||||
|
||||
COMMENT ON CAST (money AS bigint) IS 'Cast from money to bigint';
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Cast: money -> bigint
|
||||
|
||||
-- DROP CAST (money AS bigint);
|
||||
|
||||
CREATE CAST (money AS bigint)
|
||||
WITHOUT FUNCTION
|
||||
AS IMPLICIT;
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"scenarios": [
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create IMPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"castcontext": "IMPLICIT",
|
||||
"encoding": "UTF8",
|
||||
"name": "money->bigint",
|
||||
"srctyp": "money",
|
||||
"trgtyp": "bigint"
|
||||
},
|
||||
"expected_sql_file": "create_implicit_cast.sql"
|
||||
},
|
||||
{
|
||||
"type": "alter",
|
||||
"name": "Alter IMPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj_id",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"description": "Cast from money to bigint"
|
||||
},
|
||||
"expected_sql_file": "alter_implicit_cast.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop IMPLICIT Cast",
|
||||
"endpoint": "NODE-cast.delete_id",
|
||||
"data": {
|
||||
"name": "money->bigint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "create",
|
||||
"name":"Create EXPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"castcontext": "EXPLICIT",
|
||||
"encoding": "UTF8",
|
||||
"name": "money->bigint",
|
||||
"srctyp": "money",
|
||||
"trgtyp": "bigint"
|
||||
},
|
||||
"expected_sql": "-- Cast: money -> bigint\n\n-- DROP CAST (money AS bigint);\n\nCREATE CAST (money AS bigint)\n\tWITHOUT FUNCTION;"
|
||||
},
|
||||
{
|
||||
"type": "alter",
|
||||
"name": "Alter EXPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj_id",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"description": "Cast from money to bigint"
|
||||
},
|
||||
"expected_sql": "-- Cast: money -> bigint\n\n-- DROP CAST (money AS bigint);\n\nCREATE CAST (money AS bigint)\n\tWITHOUT FUNCTION;\n\nCOMMENT ON CAST (money AS bigint) IS 'Cast from money to bigint';"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop EXPLICIT Cast",
|
||||
"endpoint": "NODE-cast.delete_id",
|
||||
"data": {
|
||||
"name": "money->bigint"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -116,10 +116,6 @@ class ForeignServerView(PGChildNodeView):
|
||||
* __init__(**kwargs)
|
||||
- Method is used to initialize the ForeignServerView and it's base view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
@@ -192,23 +188,9 @@ class ForeignServerView(PGChildNodeView):
|
||||
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"foreign_servers/js/foreign_servers.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
|
||||
@@ -132,10 +132,6 @@ class UserMappingView(PGChildNodeView):
|
||||
* __init__(**kwargs)
|
||||
- Method is used to initialize the UserMappingView and it's base view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
@@ -209,23 +205,9 @@ class UserMappingView(PGChildNodeView):
|
||||
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"user_mappings/js/user_mappings.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
@@ -237,7 +237,7 @@ class SchemaView(PGChildNodeView):
|
||||
self.manager = None
|
||||
self.conn = None
|
||||
self.template_path = None
|
||||
self.template_initial = 'schema'
|
||||
self.template_initial = 'schemas'
|
||||
|
||||
@staticmethod
|
||||
def ppas_template_path(ver):
|
||||
|
||||
@@ -128,8 +128,7 @@ class CatalogObjectView(PGChildNodeView):
|
||||
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||
'sql': [{'get': 'sql'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def check_precondition(f):
|
||||
|
||||
@@ -155,8 +155,7 @@ class CatalogObjectColumnsView(PGChildNodeView):
|
||||
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||
'sql': [{'get': 'sql'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def check_precondition(f):
|
||||
|
||||
@@ -171,7 +171,6 @@ class CollationView(PGChildNodeView):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'get_collations': [{'get': 'get_collation'},
|
||||
{'get': 'get_collation'}]
|
||||
})
|
||||
@@ -193,7 +192,7 @@ class CollationView(PGChildNodeView):
|
||||
self.conn = self.manager.connection(did=kwargs['did'])
|
||||
# Set the template path for the SQL scripts
|
||||
self.template_path = compile_template_path(
|
||||
'collation/sql/',
|
||||
'collations/sql/',
|
||||
self.manager.server_type,
|
||||
self.manager.version
|
||||
)
|
||||
|
||||
@@ -11,6 +11,5 @@ from pgadmin.utils.route import BaseTestGenerator
|
||||
|
||||
|
||||
class CollationTestGenerator(BaseTestGenerator):
|
||||
|
||||
def generate_tests(self):
|
||||
def runTest(self):
|
||||
return
|
||||
@@ -95,9 +95,6 @@ class DomainView(PGChildNodeView, DataTypeReader):
|
||||
- Works as a decorator.
|
||||
Validating request on the request of create, update and modified SQL.
|
||||
|
||||
* module_js():
|
||||
- Load JS file (domains.js) for this module.
|
||||
|
||||
* check_precondition(f):
|
||||
- Works as a decorator.
|
||||
- Checks database connection status.
|
||||
@@ -168,7 +165,6 @@ class DomainView(PGChildNodeView, DataTypeReader):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'get_types': [{'get': 'types'}, {'get': 'types'}],
|
||||
'get_collations': [
|
||||
{'get': 'get_collations'},
|
||||
@@ -246,18 +242,6 @@ class DomainView(PGChildNodeView, DataTypeReader):
|
||||
|
||||
return wrap
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
Load JS file (domains.js) for this module.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"domains/js/domains.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
Works as a decorator.
|
||||
|
||||
@@ -109,9 +109,6 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
- Works as a decorator.
|
||||
Validating request on the request of create, update and modified SQL.
|
||||
|
||||
* module_js():
|
||||
- Overrides this property to define javascript for Foreign Table node.
|
||||
|
||||
* check_precondition(f):
|
||||
- Works as a decorator.
|
||||
- Checks database connection status.
|
||||
@@ -204,7 +201,6 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'get_collations': [
|
||||
{'get': 'get_collations'},
|
||||
{'get': 'get_collations'}
|
||||
@@ -319,18 +315,6 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
|
||||
return wrap
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
Load JS file (foreign_tables.js) for this module.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"foreign_tables/js/foreign_tables.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
Works as a decorator.
|
||||
@@ -1208,7 +1192,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
|
||||
if db_variables is not None:
|
||||
for row in db_variables:
|
||||
var_name, var_value = row.split("=")
|
||||
# The value may contain equals in string, split on
|
||||
# first equals only
|
||||
var_name, var_value = row.split("=", 1)
|
||||
|
||||
var_dict = {'option': var_name, 'value': var_value}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
SELECT
|
||||
oid as conoid, conname, contype, pg_get_constraintdef(oid, true) as consrc,
|
||||
connoinherit, convalidated, conislocal
|
||||
FROM
|
||||
pg_constraint
|
||||
WHERE
|
||||
conrelid={{foid}}::oid
|
||||
ORDER by conname;
|
||||
@@ -108,10 +108,6 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
- Method is used to initialize the FtsConfigurationView and it's base
|
||||
view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
@@ -200,7 +196,6 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'parsers': [{'get': 'parsers'},
|
||||
{'get': 'parsers'}],
|
||||
'copyConfig': [{'get': 'copyConfig'},
|
||||
@@ -215,18 +210,6 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
self.manager = None
|
||||
super(FtsConfigurationView, self).__init__(**kwargs)
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
Load JS file (fts_configuration.js) for this module.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"fts_configuration/js/fts_configuration.js",
|
||||
_=_
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
@@ -242,7 +225,7 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
kwargs['sid'])
|
||||
self.conn = self.manager.connection(did=kwargs['did'])
|
||||
# Set the template path for the SQL scripts
|
||||
self.template_path = 'fts_configuration/sql/#{0}#'.format(
|
||||
self.template_path = 'fts_configurations/sql/#{0}#'.format(
|
||||
self.manager.version)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||