Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
277bf85645 | ||
|
|
991fe3d797 | ||
|
|
4009ac3108 | ||
|
|
92e6a47713 | ||
|
|
e53c2b1a5d | ||
|
|
e536408ce2 | ||
|
|
f74448a1b2 | ||
|
|
9b5a6e2bb7 | ||
|
|
3fef078ec8 | ||
|
|
74af8976c9 | ||
|
|
ecded18333 | ||
|
|
5452bff49e | ||
|
|
05ae330030 | ||
|
|
94e1820ccd | ||
|
|
49b318c39e | ||
|
|
4ac064de42 | ||
|
|
2ef71cf749 | ||
|
|
d96f453e7f | ||
|
|
0b47d1dbf6 | ||
|
|
d728ee7436 | ||
|
|
1a192109d6 | ||
|
|
024cfd2fab | ||
|
|
a25bb2b7db | ||
|
|
fd3337024f | ||
|
|
ded446fe8a | ||
|
|
a9d964b5ca | ||
|
|
9c3925e448 | ||
|
|
6016a5ee98 | ||
|
|
490999f528 | ||
|
|
ceb0e39e14 | ||
|
|
3bed332439 | ||
|
|
c21ea3c342 | ||
|
|
84ecffa4d9 | ||
|
|
9bff6c7157 | ||
|
|
5c4c09f237 | ||
|
|
f706f7ed0c | ||
|
|
fb12b2e2fc | ||
|
|
badbbade08 |
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/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -88,7 +88,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 +100,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*
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 36 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: 261 KiB After Width: | Height: | Size: 247 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,7 +190,7 @@ 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
|
||||
@@ -203,13 +203,13 @@ 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 + Right | Alt + Shift + Right | Move to next inner panel |
|
||||
+--------------------------+---------------------+------------------------------+
|
||||
| Alt + Shift + Left | Alt + Shift + Left | Move to previous inner panel |
|
||||
+--------------------------+---------------------+------------------------------+
|
||||
|
||||
Access Key
|
||||
**********
|
||||
@@ -221,14 +221,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,38 @@
|
||||
.. _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*
|
||||
* Note that, if master password is disabled, then all the saved passwords will be removed.
|
||||
|
||||
.. warning:: If master password is disabled, then the saved passwords will be encrypted using a key
|
||||
which may not be as secure as master password. It is strongly recommended to use master password if you use "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 in case the application gets 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 the saved passwords and close all the existing established
|
||||
connections.
|
||||
@@ -11,6 +11,9 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_4_7
|
||||
release_notes_4_6
|
||||
release_notes_4_5
|
||||
release_notes_4_4
|
||||
release_notes_4_3
|
||||
release_notes_4_2
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
***********
|
||||
Version 4.5
|
||||
***********
|
||||
|
||||
Release date: 2019-04-10
|
||||
|
||||
This release contains a number of new features and fixes reported since the
|
||||
release of pgAdmin4 4.4.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #2214 <https://redmine.postgresql.org/issues/2214>`_ - Fixed 'Change Password' issue for SCRAM authentication.
|
||||
| `Bug #3656 <https://redmine.postgresql.org/issues/3656>`_ - Ensure that two consecutive SELECT statements should work properly.
|
||||
| `Bug #4131 <https://redmine.postgresql.org/issues/4131>`_ - Relabel the Save button on the datagrid text editor to avoid confusion with the actual Save button that updates the database.
|
||||
| `Bug #4142 <https://redmine.postgresql.org/issues/4142>`_ - Added recommended ESLinter checks.
|
||||
| `Bug #4143 <https://redmine.postgresql.org/issues/4143>`_ - Ensure that pgAdmin4 should work properly with psycopg2 v2.8
|
||||
@@ -0,0 +1,33 @@
|
||||
***********
|
||||
Version 4.6
|
||||
***********
|
||||
|
||||
Release date: 2019-05-02
|
||||
|
||||
This release contains a number of new features and fixes reported since the
|
||||
release of pgAdmin4 4.5
|
||||
|
||||
Features
|
||||
********
|
||||
|
||||
| `Feature #4165 <https://redmine.postgresql.org/issues/4165>`_ - Depend on psycopg2-binary in the Python wheel, rather than psycopg2.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #2392 <https://redmine.postgresql.org/issues/2392>`_ - Ensure that on clicking Delete button should not delete rows immediately from the database server, it should be deleted when Save button will be clicked.
|
||||
| `Bug #3327 <https://redmine.postgresql.org/issues/3327>`_ - Ensure that newly added row in backgrid should be visible.
|
||||
| `Bug #3582 <https://redmine.postgresql.org/issues/3582>`_ - Ensure that JSON strings as comments should be added properly for all the objects.
|
||||
| `Bug #3605 <https://redmine.postgresql.org/issues/3605>`_ - Fix an issue where Deleting N number of rows makes first N number of rows disable.
|
||||
| `Bug #3938 <https://redmine.postgresql.org/issues/3938>`_ - Added support for Default Partition.
|
||||
| `Bug #4087 <https://redmine.postgresql.org/issues/4087>`_ - Fix an issue where 'GRANT UPDATE' sql should be displayed for default sequence privileges.
|
||||
| `Bug #4101 <https://redmine.postgresql.org/issues/4101>`_ - Ensure that confirmation dialog should be popped up before reload of query tool or debugger if it is opened in a new browser tab.
|
||||
| `Bug #4104 <https://redmine.postgresql.org/issues/4104>`_ - Ensure that record should be add/edited for root partition table with primary keys.
|
||||
| `Bug #4121 <https://redmine.postgresql.org/issues/4121>`_ - Fixed alignment issue of columns in definition section of Index node.
|
||||
| `Bug #4134 <https://redmine.postgresql.org/issues/4134>`_ - Fixed 'Location cannot be empty' error when open Tablespace properties.
|
||||
| `Bug #4138 <https://redmine.postgresql.org/issues/4138>`_ - Fix an issue where the dropdown becomes misaligned/displaced.
|
||||
| `Bug #4154 <https://redmine.postgresql.org/issues/4154>`_ - Ensure the treeview shows all sequences except those used to implement IDENTITY columns (which can be edited as part of the column). Show all if Show System Objects is enabled.
|
||||
| `Bug #4160 <https://redmine.postgresql.org/issues/4160>`_ - Fixed 'Increment value cannot be empty' error for existing tables.
|
||||
| `Bug #4161 <https://redmine.postgresql.org/issues/4161>`_ - Ensure that parameters of procedures for EPAS server 10 and below should be set/reset properly.
|
||||
| `Bug #4163 <https://redmine.postgresql.org/issues/4163>`_ - Prevent duplicate columns being included in reverse engineered SQL for tables.
|
||||
| `Bug #4182 <https://redmine.postgresql.org/issues/4182>`_ - Ensure sanity of the permissions on the storage and session directories and the config database.
|
||||
@@ -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.
|
||||
@@ -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*.
|
||||
@@ -442,6 +443,7 @@ icon (+) to add each partition:
|
||||
* Move the *Operation* switch to *attach* to attach the partition, by default it
|
||||
is *create*.
|
||||
* Use the *Name* field to add the name of the partition.
|
||||
* If partition type is Range or List then *Default* field will be enabled.
|
||||
* If partition type is Range then *From* and *To* fields will be enabled.
|
||||
* If partition type is List then *In* field will be enabled.
|
||||
* If partition type is Hash then *Modulus* and *Remainder* fields will be
|
||||
@@ -523,4 +525,4 @@ three columns and a primary key constraint on the *category_id* column.
|
||||
* Click the *Info* button (i) to access online help.
|
||||
* Click the *Save* button to save work.
|
||||
* Click the *Cancel* button to exit without saving work.
|
||||
* Click the *Reset* button to restore configuration parameters.
|
||||
* Click the *Reset* button to restore configuration parameters.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ for src in "${DMG_SOURCES}"; do
|
||||
done
|
||||
|
||||
echo "Creating image"
|
||||
${HDIUTIL} create -quiet -srcfolder "$DMG_DIR" -format UDZO -volname "${DMG_VOLUME_NAME}" -ov "${DMG_IMAGE}" || exit 1
|
||||
${HDIUTIL} create -quiet -srcfolder "$DMG_DIR" -fs HFS+ -format UDZO -volname "${DMG_VOLUME_NAME}" -ov "${DMG_IMAGE}" || exit 1
|
||||
rm -rf "${DMG_DIR}" || exit 1
|
||||
|
||||
echo "Attaching License to image"
|
||||
|
||||
@@ -37,6 +37,11 @@ with open(req_file, 'r') as reqf:
|
||||
for index, req in enumerate(required):
|
||||
if ";" in req or req.startswith("#") or req == "":
|
||||
required.remove(req)
|
||||
continue
|
||||
|
||||
# Ensure the Wheel will use psycopg2-binary, not the source distro
|
||||
if 'psycopg2' in req:
|
||||
required[index] = req.replace('psycopg2', 'psycopg2-binary')
|
||||
|
||||
# Get the app version
|
||||
if sys.version_info[:2] >= (3, 3):
|
||||
|
||||
@@ -21,7 +21,7 @@ sqlparse==0.2.4
|
||||
WTForms==2.2.1
|
||||
Flask-Paranoid==0.2.0
|
||||
psutil==5.5.1
|
||||
psycopg2>=2.7.7
|
||||
psycopg2>=2.8
|
||||
python-dateutil>=2.8.0
|
||||
htmlmin==0.1.12
|
||||
Flask-HTMLmin==1.5.0
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<key>CFBundleName</key>
|
||||
<string>pgAdmin 4</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.4.0</string>
|
||||
<string>4.7.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4.4.0</string>
|
||||
<string>4.7.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION = 4.4.0.0
|
||||
VERSION = 4.7.0.0
|
||||
QMAKE_TARGET_COMPANY = "The pgAdmin Development Team"
|
||||
QMAKE_TARGET_PRODUCT = "pgAdmin 4"
|
||||
QMAKE_TARGET_DESCRIPTION = "pgAdmin 4 Desktop Runtime"
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
# Get the correct version of chromedriver for the version of Chrome on the
|
||||
# machine and save it to the specified location.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
|
||||
def read_command_line():
|
||||
"""Read the command line arguments.
|
||||
|
||||
Returns:
|
||||
ArgumentParser: The parsed arguments object
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Get the correct version of chromedriver for the '
|
||||
'specified Chrome installation and save it.')
|
||||
if platform.system() != 'Windows':
|
||||
parser.add_argument("chrome", metavar="CHROME",
|
||||
help="the Chrome executable")
|
||||
parser.add_argument("directory", metavar="DIRECTORY",
|
||||
help="the directory in which to save chromedriver")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def get_chrome_version(args):
|
||||
"""Get the Chrome version number.
|
||||
|
||||
Args:
|
||||
args: The parsed arguments object
|
||||
Returns:
|
||||
The Chrome version number
|
||||
"""
|
||||
if platform.system() == 'Windows':
|
||||
# On Windows we need to examine the resource section of the binary
|
||||
import winreg
|
||||
|
||||
def _read_registry(root, key, value):
|
||||
try:
|
||||
hkey = winreg.OpenKey(root, key)
|
||||
except Exception as e:
|
||||
return None
|
||||
try:
|
||||
(val, typ) = winreg.QueryValueEx(hkey, value)
|
||||
except Exception as e:
|
||||
winreg.CloseKey(hkey)
|
||||
return None
|
||||
|
||||
winreg.CloseKey(hkey)
|
||||
return val
|
||||
|
||||
key = r'SOFTWARE\Google\Chrome\BLBeacon'
|
||||
version_str = _read_registry(winreg.HKEY_CURRENT_USER, key, 'Version')
|
||||
|
||||
# On a 64b host, look for a 32b installation
|
||||
if not version_str:
|
||||
key = r'SOFTWARE\Wow6432Node\Google\Chrome\BLBeacon'
|
||||
version_str = _read_registry(winreg.HKEY_CURRENT_USER, key,
|
||||
'Version')
|
||||
|
||||
if not version_str:
|
||||
print('The Chrome version could not be read from the registry.')
|
||||
sys.exit(1)
|
||||
|
||||
chrome_version = '.'.join(version_str.split()[-1].split('.')[:-1])
|
||||
else:
|
||||
# On Linux/Mac we run the Chrome executable with the --version flag,
|
||||
# then parse the output.
|
||||
try:
|
||||
result = subprocess.run([args.chrome, '--version'],
|
||||
stdout=subprocess.PIPE)
|
||||
except FileNotFoundError as e:
|
||||
print('The specified Chrome executable could not be found.')
|
||||
sys.exit(1)
|
||||
|
||||
version_str = result.stdout.decode("utf-8").strip()
|
||||
# Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
|
||||
if "Chrom" not in version_str:
|
||||
print('The specified Chrome executable output an unexpected '
|
||||
'version string: {}.'.format(version_str))
|
||||
sys.exit(1)
|
||||
|
||||
chrome_version = '.'.join(version_str.split()[-1].split('.')[:-1])
|
||||
|
||||
if chrome_version.count('.') != 2:
|
||||
print('The specified Chrome executable output an unexpected '
|
||||
'version string: {}.'.format(version_str))
|
||||
sys.exit(1)
|
||||
|
||||
return chrome_version
|
||||
|
||||
|
||||
def get_chromedriver_version(chrome_version):
|
||||
"""Get the required chromedriver version number.
|
||||
|
||||
Args:
|
||||
chrome_version: The chrome version number
|
||||
Returns:
|
||||
The chromedriver version number
|
||||
"""
|
||||
url = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{}' \
|
||||
.format(chrome_version)
|
||||
|
||||
try:
|
||||
fp = urllib.request.urlopen(url)
|
||||
except urllib.error.URLError as e:
|
||||
print('The chromedriver catalog URL could not be accessed: {}'
|
||||
.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
version = fp.read().decode("utf-8").strip()
|
||||
fp.close()
|
||||
|
||||
return version
|
||||
|
||||
|
||||
def get_system():
|
||||
"""Get the system name as known to chromedriver
|
||||
|
||||
Returns:
|
||||
The system name
|
||||
"""
|
||||
if platform.system() == 'Darwin':
|
||||
return 'mac64'
|
||||
elif platform.system() == 'Linux':
|
||||
return 'linux64'
|
||||
elif platform.system() == 'Windows':
|
||||
return 'win32'
|
||||
else:
|
||||
print("Unknown or unsupported operating system: {}"
|
||||
.format(platform.system()))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
"""The core structure of the app."""
|
||||
|
||||
# Read the command line options
|
||||
args = read_command_line()
|
||||
|
||||
chrome_version = get_chrome_version(args)
|
||||
|
||||
# Check the directory exists
|
||||
if not os.path.isdir(args.directory):
|
||||
print('The specified output directory could not be accessed.')
|
||||
sys.exit(1)
|
||||
|
||||
chromedriver_version = get_chromedriver_version(chrome_version)
|
||||
|
||||
system = get_system()
|
||||
|
||||
url = 'https://chromedriver.storage.googleapis.com/{}/chromedriver_{}.zip' \
|
||||
.format(chromedriver_version, system)
|
||||
|
||||
print('Downloading chromedriver v{} for Chrome v{} on {}...'
|
||||
.format(chromedriver_version, chrome_version, system))
|
||||
|
||||
try:
|
||||
file, headers = urllib.request.urlretrieve(url)
|
||||
except urllib.error.URLError as e:
|
||||
print('The chromedriver download URL could not be accessed: {}'
|
||||
.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
# Unzip chromedriver
|
||||
print('Extracting chromedriver...')
|
||||
|
||||
found = False
|
||||
fp = open(file, 'rb')
|
||||
z = zipfile.ZipFile(fp)
|
||||
for name in z.namelist():
|
||||
if (system == 'win32' and name == 'chromedriver.exe') or \
|
||||
(system != 'win32' and name == 'chromedriver'):
|
||||
z.extract(name, args.directory)
|
||||
found = True
|
||||
fp.close()
|
||||
|
||||
if not found:
|
||||
print("chromedriver could not be found in the downloaded archive: {}"
|
||||
.format(file))
|
||||
sys.exit(1)
|
||||
|
||||
# Set the permissions
|
||||
if system == 'mac64' or system == 'linux64':
|
||||
os.chmod(os.path.join(args.directory, 'chromedriver'), 0o755)
|
||||
|
||||
print('Chromedriver downloaded to: {}'.format(args.directory))
|
||||
@@ -14,6 +14,9 @@ module.exports = {
|
||||
'amd': true,
|
||||
'jasmine': true,
|
||||
},
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
],
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018,
|
||||
'sourceType': 'module',
|
||||
@@ -43,5 +46,7 @@ module.exports = {
|
||||
'always-multiline'
|
||||
],
|
||||
'no-console': ["error", { allow: ["warn", "error"] }],
|
||||
// We need to exclude below for RegEx case
|
||||
"no-useless-escape": 0,
|
||||
},
|
||||
};
|
||||
@@ -50,7 +50,7 @@ APP_ICON = 'pg-icon'
|
||||
|
||||
# Application version number components
|
||||
APP_RELEASE = 4
|
||||
APP_REVISION = 4
|
||||
APP_REVISION = 7
|
||||
|
||||
# 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 = 40400
|
||||
APP_VERSION_INT = 40700
|
||||
|
||||
# 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:
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"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",
|
||||
@@ -75,7 +75,6 @@
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.23",
|
||||
"mousetrap": "^1.6.3",
|
||||
"prop-types": "^15.7.2",
|
||||
"requirejs": "~2.3.6",
|
||||
"select2": "^4.0.6-rc.1",
|
||||
"shim-loader": "^1.0.1",
|
||||
|
||||
@@ -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
|
||||
@@ -338,6 +339,9 @@ def create_app(app_name=None):
|
||||
set_version(CURRENT_SCHEMA_VERSION)
|
||||
db.session.commit()
|
||||
|
||||
if os.name != 'nt':
|
||||
os.chmod(config.SQLITE_PATH, 0o600)
|
||||
|
||||
Mail(app)
|
||||
|
||||
import pgadmin.utils.paths as paths
|
||||
@@ -364,7 +368,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)
|
||||
@@ -567,12 +574,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)
|
||||
@@ -623,6 +641,12 @@ 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_app.keyManager.get() is None:
|
||||
logout_user()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
if 'key' in request.args:
|
||||
@@ -703,8 +727,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,13 @@ 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
|
||||
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
|
||||
@@ -219,7 +223,10 @@ 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']
|
||||
|
||||
|
||||
blueprint = BrowserModule(MODULE_NAME, __name__)
|
||||
@@ -478,6 +485,7 @@ class BrowserPluginModule(PgAdminModule):
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def index():
|
||||
"""Render and process the main browser window."""
|
||||
@@ -561,6 +569,7 @@ def index():
|
||||
|
||||
|
||||
@blueprint.route("/js/utils.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def utils():
|
||||
layout = get_setting('Browser/Layout', default='')
|
||||
@@ -623,61 +632,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 +688,133 @@ 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,
|
||||
)
|
||||
|
||||
|
||||
# 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 +823,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 +873,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 +916,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 +983,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:
|
||||
|
||||
@@ -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(
|
||||
@@ -805,7 +804,12 @@ class DatabaseView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('comments',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
status, res = self.get_sql(gid, sid, data, did)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -555,7 +555,12 @@ class ForeignDataWrapperView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
try:
|
||||
|
||||
@@ -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
|
||||
@@ -578,7 +560,12 @@ class ForeignServerView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
try:
|
||||
|
||||
@@ -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
|
||||
@@ -558,7 +558,12 @@ class LanguageView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
try:
|
||||
|
||||
@@ -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):
|
||||
@@ -724,7 +724,12 @@ It may have been removed by another user.
|
||||
data = dict()
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -602,7 +601,12 @@ class CollationView(PGChildNodeView):
|
||||
data = dict()
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
@@ -612,7 +595,12 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
|
||||
@@ -105,10 +105,6 @@ class FtsDictionaryView(PGChildNodeView):
|
||||
* __init__(**kwargs)
|
||||
- Method is used to initialize the FtsDictionaryView 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,7 +188,6 @@ class FtsDictionaryView(PGChildNodeView):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'fetch_templates': [{'get': 'fetch_templates'},
|
||||
{'get': 'fetch_templates'}],
|
||||
})
|
||||
@@ -203,18 +198,6 @@ class FtsDictionaryView(PGChildNodeView):
|
||||
self.manager = None
|
||||
super(FtsDictionaryView, self).__init__(**kwargs)
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
Load JS file (fts_dictionary.js) for this module.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"fts_dictionary/js/fts_dictionary.js",
|
||||
_=_
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
@@ -232,7 +215,7 @@ class FtsDictionaryView(PGChildNodeView):
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
self.qtIdent = driver.qtIdent
|
||||
# Set the template path for the SQL scripts
|
||||
self.template_path = 'fts_dictionary/sql/#{0}#'.format(
|
||||
self.template_path = 'fts_dictionaries/sql/#{0}#'.format(
|
||||
self.manager.version)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
@@ -607,7 +590,12 @@ class FtsDictionaryView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
|
||||