diff --git a/docs/en_US/release_notes_4_15.rst b/docs/en_US/release_notes_4_15.rst index 06e146077..0601f6353 100644 --- a/docs/en_US/release_notes_4_15.rst +++ b/docs/en_US/release_notes_4_15.rst @@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o New features ************ +| `Issue #1974 `_ - Added encrypted password in reverse engineered SQL for roles. Housekeeping ************ diff --git a/web/pgadmin/browser/server_groups/servers/roles/__init__.py b/web/pgadmin/browser/server_groups/servers/roles/__init__.py index 4d83f650e..8dbaf2a6a 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/roles/__init__.py @@ -735,9 +735,10 @@ rolmembership:{ @check_precondition() def sql(self, gid, sid, rid): + show_password = self.conn.manager.user_info['is_superuser'] status, res = self.conn.execute_scalar( render_template( - self.sql_path + 'sql.sql' + self.sql_path + 'sql.sql', show_password=show_password ), dict({'rid': rid}) ) diff --git a/web/pgadmin/browser/server_groups/servers/roles/templates/roles/sql/9.4_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/roles/templates/roles/sql/9.4_plus/sql.sql index cda36c336..b9ad7cf72 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/templates/roles/sql/9.4_plus/sql.sql +++ b/web/pgadmin/browser/server_groups/servers/roles/templates/roles/sql/9.4_plus/sql.sql @@ -8,7 +8,6 @@ FROM pg_catalog.quote_ident(rolname) || E';\n\nCREATE ROLE ' || pg_catalog.quote_ident(rolname) || E' WITH\n ' || CASE WHEN rolcanlogin THEN 'LOGIN' ELSE 'NOLOGIN' END || E'\n ' || - CASE WHEN rolcanlogin AND rolpassword LIKE 'md5%%' THEN 'ENCRYPTED PASSWORD ' || quote_literal(rolpassword) || E'\n ' ELSE '' END || CASE WHEN rolsuper THEN 'SUPERUSER' ELSE 'NOSUPERUSER' END || E'\n ' || CASE WHEN rolinherit THEN 'INHERIT' ELSE 'NOINHERIT' END || E'\n ' || CASE WHEN rolcreatedb THEN 'CREATEDB' ELSE 'NOCREATEDB' END || E'\n ' || @@ -16,6 +15,12 @@ FROM -- PostgreSQL >= 9.1 CASE WHEN rolreplication THEN 'REPLICATION' ELSE 'NOREPLICATION' END || CASE WHEN rolconnlimit > 0 THEN E'\n CONNECTION LIMIT ' || rolconnlimit ELSE '' END || +{% if show_password %} + (SELECT CASE + WHEN (rolpassword LIKE 'md5%%' or rolpassword LIKE 'SCRAM%%') THEN E'\n ENCRYPTED PASSWORD ''' || rolpassword || '''' + WHEN rolpassword IS NOT NULL THEN E'\n PASSWORD ''' || rolpassword || '''' + ELSE '' END FROM pg_authid au WHERE au.oid=r.oid) || +{% endif %} CASE WHEN rolvaliduntil IS NOT NULL THEN E'\n VALID UNTIL ' || quote_literal(rolvaliduntil::text) ELSE '' END || ';' AS sql FROM pg_roles r diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_login_role_options.sql b/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_login_role_options.sql index 04ac56221..c04e71d13 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_login_role_options.sql +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_login_role_options.sql @@ -9,6 +9,7 @@ CREATE ROLE "Role2_$%{}[]()&*^!@""'`\/#" WITH CREATEROLE NOREPLICATION CONNECTION LIMIT 100 + ENCRYPTED PASSWORD '' VALID UNTIL ''; ALTER ROLE "Role2_$%{}[]()&*^!@""'`\/#" IN DATABASE postgres SET application_name TO 'pg4'; diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_role_options.sql b/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_role_options.sql index f1572037e..9dc370458 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_role_options.sql +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/alter_role_options.sql @@ -9,6 +9,7 @@ CREATE ROLE "Role2_$%{}[]()&*^!@""'`\/#" WITH NOCREATEROLE NOREPLICATION CONNECTION LIMIT 100 + ENCRYPTED PASSWORD '' VALID UNTIL ''; ALTER ROLE "Role2_$%{}[]()&*^!@""'`\/#" IN DATABASE postgres SET application_name TO 'pg4'; diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/test.json b/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/test.json index 3de2d40ac..2e184feef 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/test.json +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/9.4_plus/test.json @@ -65,7 +65,8 @@ }, "expected_sql_file": "alter_role_options.sql", "expected_msql_file": "alter_role_options.msql", - "convert_timestamp_columns": ["rolvaliduntil"] + "convert_timestamp_columns": ["rolvaliduntil"], + "replace_password": true }, { "type": "delete", @@ -138,7 +139,8 @@ }, "expected_sql_file": "alter_login_role_options.sql", "expected_msql_file": "alter_login_role_options.msql", - "convert_timestamp_columns": ["rolvaliduntil"] + "convert_timestamp_columns": ["rolvaliduntil"], + "replace_password": true }, { "type": "delete", diff --git a/web/regression/re_sql/tests/test_resql.py b/web/regression/re_sql/tests/test_resql.py index e9de05cac..fc1e59473 100644 --- a/web/regression/re_sql/tests/test_resql.py +++ b/web/regression/re_sql/tests/test_resql.py @@ -9,6 +9,7 @@ from __future__ import print_function import json import os +import re import traceback from flask import url_for import regression @@ -103,7 +104,8 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator): # while running the test cases self.JSON_PLACEHOLDERS = {'schema_id': '', 'owner': '', - 'timestamptz': ''} + 'timestamptz': '', + 'password': ''} resql_module_list = create_resql_module_list( BaseTestGenerator.re_sql_module_list, @@ -416,14 +418,7 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator): fp = open(output_file, "r") # Used rstrip to remove trailing \n sql = fp.read().rstrip() - # Replace place holder with the current username - # used to connect to the database - if 'username' in self.server: - sql = sql.replace(self.JSON_PLACEHOLDERS['owner'], - self.server['username']) - # Convert timestamp with timezone from json file to the - # database server's correct timestamp - sql = self.convert_timestamptz(scenario, sql) + sql = self.preprocess_expected_sql(scenario, sql, resp_sql) try: self.assertEquals(sql, resp_sql) except Exception as e: @@ -477,14 +472,7 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator): fp = open(output_file, "r") # Used rstrip to remove trailing \n sql = fp.read().rstrip() - # Replace place holder with the current username - # used to connect to the database - if 'username' in self.server: - sql = sql.replace(self.JSON_PLACEHOLDERS['owner'], - self.server['username']) - # Convert timestamp with timezone from json file to the - # database server's correct timestamp - sql = self.convert_timestamptz(scenario, sql) + sql = self.preprocess_expected_sql(scenario, sql, resp_sql) try: self.assertEquals(sql, resp_sql) except Exception as e: @@ -500,14 +488,7 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator): return False elif 'expected_sql' in scenario: exp_sql = scenario['expected_sql'] - # Replace place holder with the current username - # used to connect to the database - if 'username' in self.server: - exp_sql = exp_sql.replace(self.JSON_PLACEHOLDERS['owner'], - self.server['username']) - # Convert timestamp with timezone from json file to the - # database server's correct timestamp - sql = self.convert_timestamptz(scenario, exp_sql) + exp_sql = self.preprocess_expected_sql(scenario, exp_sql, resp_sql) try: self.assertEquals(exp_sql, resp_sql) except Exception as e: @@ -643,6 +624,38 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator): return data + def preprocess_expected_sql(self, scenario, sql, resp_sql): + """ + This function preprocesses expected sql before comparing + it with response sql. + :param data: sql + :param data: resp_sql + :return: + """ + # Replace place holder with the current username + # used to connect to the database + if 'username' in self.server: + sql = sql.replace(self.JSON_PLACEHOLDERS['owner'], + self.server['username']) + # Convert timestamp with timezone from json file to the + # database server's correct timestamp + sql = self.convert_timestamptz(scenario, sql) + + # extract password fields from response and replace in expected + # to match the response + if 'replace_password' in scenario: + password = '' + for line in resp_sql.split('\n'): + if 'PASSWORD' in line: + found = re.search("'([\w\W]*)'", line) + if found: + password = found.groups(0)[0] + break + + sql = sql.replace(self.JSON_PLACEHOLDERS['password'], password) + + return sql + def replace_placeholder_with_id(self, value): """ This function is used to replace the place holder with id.