diff --git a/web/pgadmin/browser/server_groups/servers/resource_groups/__init__.py b/web/pgadmin/browser/server_groups/servers/resource_groups/__init__.py index 119a6ff..81e4571 100644 --- a/web/pgadmin/browser/server_groups/servers/resource_groups/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/resource_groups/__init__.py @@ -344,6 +344,19 @@ class ResourceGroupView(NodeView): status=200 ) + @staticmethod + def _check_req_parameters(data, required_args): + for arg in required_args: + if arg not in data: + return True, make_json_response( + status=410, + success=0, + errormsg=gettext( + "Could not find the required parameter ({})." + ).format(arg) + ) + return False, '' + @check_precondition def create(self, gid, sid): """ @@ -360,15 +373,13 @@ class ResourceGroupView(NodeView): data = request.form if request.form else json.loads( request.data, encoding='utf-8' ) - for arg in required_args: - if arg not in data: - return make_json_response( - status=410, - success=0, - errormsg=gettext( - "Could not find the required parameter ({})." - ).format(arg) - ) + + is_error, errmsg = ResourceGroupView._check_req_parameters( + data, required_args) + + if is_error: + return errmsg + try: # Below logic will create new resource group sql = render_template( @@ -416,6 +427,22 @@ class ResourceGroupView(NodeView): except Exception as e: return internal_server_error(errormsg=str(e)) + def _check_cup_and_dirty_rate_limit(self, data, old_data): + # Below logic will update the cpu_rate_limit and dirty_rate_limit + # for resource group we need to add this logic because in + # resource group you can't run multiple commands in one + # transaction. + if data['cpu_rate_limit'] != old_data['cpu_rate_limit'] or \ + data['dirty_rate_limit'] != old_data['dirty_rate_limit']: + sql = render_template( + "/".join([self.sql_path, 'update.sql']), + data=data, conn=self.conn + ) + if sql and sql.strip('\n') and sql.strip(' '): + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + @check_precondition def update(self, gid, sid, rg_id): """ @@ -459,16 +486,7 @@ class ResourceGroupView(NodeView): # for resource group we need to add this logic because in # resource group you can't run multiple commands in one # transaction. - if data['cpu_rate_limit'] != old_data['cpu_rate_limit'] or \ - data['dirty_rate_limit'] != old_data['dirty_rate_limit']: - sql = render_template( - "/".join([self.sql_path, 'update.sql']), - data=data, conn=self.conn - ) - if sql and sql.strip('\n') and sql.strip(' '): - status, res = self.conn.execute_scalar(sql) - if not status: - return internal_server_error(errormsg=res) + self._check_cup_and_dirty_rate_limit(data, old_data) return jsonify( node=self.blueprint.generate_browser_node( @@ -571,6 +589,45 @@ class ResourceGroupView(NodeView): status=200 ) + def _get_sql_for_res_gp(self, rg_id, data, required_args): + """ + Get sql for resource group. + """ + sql = render_template( + "/".join([self.sql_path, 'properties.sql']), rgid=rg_id) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return gone( + gettext("The specified resource group could not be found.") + ) + old_data = res['rows'][0] + for arg in required_args: + if arg not in data: + data[arg] = old_data[arg] + + sql = '' + name_changed = False + if data['name'] != old_data['name']: + name_changed = True + sql = render_template( + "/".join([self.sql_path, 'update.sql']), + oldname=old_data['name'], newname=data['name'], + conn=self.conn + ) + if data['cpu_rate_limit'] != old_data['cpu_rate_limit'] or \ + data['dirty_rate_limit'] != old_data['dirty_rate_limit']: + if name_changed: + sql += "\n-- Following query will be executed in a " \ + "separate transaction\n" + sql += render_template( + "/".join([self.sql_path, 'update.sql']), + data=data, conn=self.conn + ) + return sql + def get_sql(self, data, rg_id=None): """ This function will generate sql from model data @@ -583,39 +640,8 @@ class ResourceGroupView(NodeView): 'name', 'cpu_rate_limit', 'dirty_rate_limit' ] if rg_id is not None: - sql = render_template( - "/".join([self.sql_path, 'properties.sql']), rgid=rg_id) - status, res = self.conn.execute_dict(sql) - if not status: - return internal_server_error(errormsg=res) - - if len(res['rows']) == 0: - return gone( - gettext("The specified resource group could not be found.") - ) - old_data = res['rows'][0] - for arg in required_args: - if arg not in data: - data[arg] = old_data[arg] - - sql = '' - name_changed = False - if data['name'] != old_data['name']: - name_changed = True - sql = render_template( - "/".join([self.sql_path, 'update.sql']), - oldname=old_data['name'], newname=data['name'], - conn=self.conn - ) - if data['cpu_rate_limit'] != old_data['cpu_rate_limit'] or \ - data['dirty_rate_limit'] != old_data['dirty_rate_limit']: - if name_changed: - sql += "\n-- Following query will be executed in a " \ - "separate transaction\n" - sql += render_template( - "/".join([self.sql_path, 'update.sql']), - data=data, conn=self.conn - ) + # Get sql for Resource group by ID. + sql = self._get_sql_for_res_gp(rg_id, data, required_args) else: sql = render_template( "/".join([self.sql_path, 'create.sql']), diff --git a/web/pgadmin/browser/server_groups/servers/roles/__init__.py b/web/pgadmin/browser/server_groups/servers/roles/__init__.py index dcc7909..7625331 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/roles/__init__.py @@ -111,6 +111,142 @@ class RoleView(PGChildNodeView): 'variables': [{'get': 'variables'}], }) + @staticmethod + def _get_request_data(): + """ + Get data from client request. + """ + if request.data: + data = json.loads(request.data, encoding='utf-8') + else: + data = dict() + req = request.args or request.form + + for key in req: + + val = req[key] + if key in [ + u'rolcanlogin', u'rolsuper', u'rolcreatedb', + u'rolcreaterole', u'rolinherit', u'rolreplication', + u'rolcatupdate', u'variables', u'rolmembership', + u'seclabels' + ]: + data[key] = json.loads(val, encoding='utf-8') + else: + data[key] = val + return data + + @staticmethod + def _check_roleconnlimit(data): + """ + Check connection limit for role. + """ + if u'rolconnlimit' in data: + # If roleconnlimit is empty string then set it to -1 + if data[u'rolconnlimit'] == '': + data[u'rolconnlimit'] = -1 + + if data[u'rolconnlimit'] is not None: + data[u'rolconnlimit'] = int(data[u'rolconnlimit']) + if type(data[u'rolconnlimit']) != int or \ + data[u'rolconnlimit'] < -1: + return True, "Connection limit must be an integer value " \ + "or equal to -1." + + return False, '' + + @staticmethod + def _check_role(data): + """ + Check user role + """ + msg = _(""" + Role membership information must be passed as an array of JSON objects + in the following format: + + rolmembership:[{ + role: [rolename], + admin: True/False + }, + ... + ]""") + if type(data[u'rolmembership']) != list: + return True, msg + + data[u'members'] = [] + data[u'admins'] = [] + + for r in data[u'rolmembership']: + if type(r) != dict or u'role' not in r or \ + u'admin' not in r: + return True, msg + else: + if r[u'admin']: + data[u'admins'].append(r[u'role']) + else: + data[u'members'].append(r[u'role']) + return False, '' + + @staticmethod + def _check_precondition_added(data): + """ + Check for pre condition for added + """ + if u'added' in data[u'rolmembership']: + roles = (data[u'rolmembership'])[u'added'] + + if type(roles) != list: + return True + + for r in roles: + if type(r) != dict or \ + u'role' not in r or \ + u'admin' not in r: + return True + + if r[u'admin']: + data[u'admins'].append(r[u'role']) + else: + data[u'members'].append(r[u'role']) + return False + + @staticmethod + def _check_precondition_deleted(data): + if u'deleted' in data[u'rolmembership']: + roles = (data[u'rolmembership'])[u'deleted'] + + if type(roles) != list: + return True + + for r in roles: + if type(r) != dict or u'role' not in r: + return True + + data[u'revoked'].append(r[u'role']) + + return False + + @staticmethod + def _check_precondition_change(data): + if u'changed' in data[u'rolmembership']: + roles = (data[u'rolmembership'])[u'changed'] + + if type(roles) != list: + return True + + for r in roles: + if type(r) != dict or \ + u'role' not in r or \ + u'admin' not in r: + return True + + if not r[u'admin']: + data[u'revoked_admins'].append(r[u'role']) + else: + data[u'admins'].append(r[u'role']) + + return False + def validate_request(f): @wraps(f) def wrap(self, **kwargs): @@ -444,6 +580,81 @@ rolmembership:{ return wrap + @staticmethod + def _check_action(action, kwargs): + check_permission = False + fetch_name = False + forbidden_msg = None + if action in ['drop', 'update']: + if 'rid' in kwargs: + fetch_name = True + check_permission = True + + if action == 'drop': + forbidden_msg = _( + "The current user does not have permission to drop" + " the role." + ) + else: + forbidden_msg = _( + "The current user does not have permission to " + "update the role." + ) + elif action == 'create': + check_permission = True + forbidden_msg = _( + "The current user does not have permission to create " + "the role." + ) + elif action == 'msql' and 'rid' in kwargs: + fetch_name = True + + return fetch_name, check_permission, forbidden_msg + + def _check_permission(self, check_permission, action, kwargs): + if check_permission: + user = self.manager.user_info + + if not user['is_superuser'] and \ + not user['can_create_role'] and \ + (action != 'update' or 'rid' in kwargs) and \ + kwargs['rid'] != -1 and \ + user['id'] != kwargs['rid']: + return True + return False + + def _check_and_fetch_name(self, fetch_name, kwargs): + if fetch_name: + status, res = self.conn.execute_dict( + render_template( + self.sql_path + 'permission.sql', + rid=kwargs['rid'], + conn=self.conn + ) + ) + + if not status: + return True, internal_server_error( + _( + "Error retrieving the role information.\n{0}" + ).format(res) + ) + + if len(res['rows']) == 0: + return True, gone( + _("Could not find the role on the database " + "server.") + ) + + row = res['rows'][0] + + self.role = row['rolname'] + self.rolCanLogin = row['rolcanlogin'] + self.rolCatUpdate = row['rolcatupdate'] + self.rolSuper = row['rolsuper'] + + return False, '' + def check_precondition(action=None): """ This function will behave as a decorator which will checks the status @@ -488,72 +699,18 @@ rolmembership:{ u'rolvaliduntil', u'rolpassword' ] - check_permission = False - fetch_name = False - forbidden_msg = None - - if action in ['drop', 'update']: - if 'rid' in kwargs: - fetch_name = True - check_permission = True - - if action == 'drop': - forbidden_msg = _( - "The current user does not have permission to drop" - " the role." - ) - else: - forbidden_msg = _( - "The current user does not have permission to " - "update the role." - ) - elif action == 'create': - check_permission = True - forbidden_msg = _( - "The current user does not have permission to create " - "the role." - ) - elif action == 'msql' and 'rid' in kwargs: - fetch_name = True - - if check_permission: - user = self.manager.user_info - - if not user['is_superuser'] and \ - not user['can_create_role'] and \ - (action != 'update' or 'rid' in kwargs) and \ - kwargs['rid'] != -1 and \ - user['id'] != kwargs['rid']: - return forbidden(forbidden_msg) - - if fetch_name: - status, res = self.conn.execute_dict( - render_template( - self.sql_path + 'permission.sql', - rid=kwargs['rid'], - conn=self.conn - ) - ) - - if not status: - return internal_server_error( - _( - "Error retrieving the role information.\n{0}" - ).format(res) - ) - - if len(res['rows']) == 0: - return gone( - _("Could not find the role on the database " - "server.") - ) + fetch_name, check_permission, \ + forbidden_msg = RoleView._check_action(action, kwargs) - row = res['rows'][0] + is_permission_error = self._check_permission(check_permission, + action, kwargs) + if is_permission_error: + return forbidden(forbidden_msg) - self.role = row['rolname'] - self.rolCanLogin = row['rolcanlogin'] - self.rolCatUpdate = row['rolcatupdate'] - self.rolSuper = row['rolsuper'] + is_error, errmsg = self._check_and_fetch_name(fetch_name, + kwargs) + if is_error: + return errmsg return f(self, **kwargs) @@ -932,6 +1089,74 @@ rolmembership:{ status=200 ) + @staticmethod + def _handel_dependents_type(types, type_str, type_name, rel_name, row): + if types[type_str[0]] is None: + if type_str[0] == 'i': + type_name = 'index' + rel_name = row['indname'] + ' ON ' + rel_name + elif type_str[0] == 'o': + type_name = 'operator' + rel_name = row['relname'] + else: + type_name = types[type_str[0]] + + return type_name, rel_name + + @staticmethod + def _handel_dependents_data(result, types, dependents, db_row): + for row in result['rows']: + rel_name = row['nspname'] + if rel_name is not None: + rel_name += '.' + + if rel_name is None: + rel_name = row['relname'] + else: + rel_name += row['relname'] + + type_name = '' + type_str = row['relkind'] + # Fetch the type name from the dictionary + # if type is not present in the types dictionary then + # we will continue and not going to add it. + if type_str[0] in types: + # if type is present in the types dictionary, but it's + # value is None then it requires special handling. + type_name, rel_name = RoleView._handel_dependents_type( + types, type_str, type_name, rel_name, row) + else: + continue + + dependents.append( + { + 'type': type_name, + 'name': rel_name, + 'field': db_row['datname'] + } + ) + + def _temp_connection_check(self, rid, temp_conn, db_row, types, + dependents): + if temp_conn.connected(): + query = render_template( + "/".join([self.sql_path, 'dependents.sql']), + fetch_dependents=True, rid=rid, + lastsysoid=db_row['datlastsysoid'] + ) + + status, result = temp_conn.execute_dict(query) + if not status: + current_app.logger.error(result) + + RoleView._handel_dependents_data(result, types, dependents, db_row) + + @staticmethod + def _release_connection(is_connected, manager, db_row): + # Release only those connections which we have created above. + if not is_connected: + manager.release(db_row['datname']) + def get_dependents(self, conn, sid, rid): """ This function is used to fetch the dependents for the selected node. @@ -1009,59 +1234,9 @@ rolmembership:{ except Exception as e: current_app.logger.exception(e) - if temp_conn.connected(): - query = render_template( - "/".join([self.sql_path, 'dependents.sql']), - fetch_dependents=True, rid=rid, - lastsysoid=db_row['datlastsysoid'] - ) - - status, result = temp_conn.execute_dict(query) - if not status: - current_app.logger.error(result) - - for row in result['rows']: - rel_name = row['nspname'] - if rel_name is not None: - rel_name += '.' - - if rel_name is None: - rel_name = row['relname'] - else: - rel_name += row['relname'] - - type_name = '' - type_str = row['relkind'] - # Fetch the type name from the dictionary - # if type is not present in the types dictionary then - # we will continue and not going to add it. - if type_str[0] in types: - - # if type is present in the types dictionary, but it's - # value is None then it requires special handling. - if types[type_str[0]] is None: - if type_str[0] == 'i': - type_name = 'index' - rel_name = row['indname'] + ' ON ' + rel_name - elif type_str[0] == 'o': - type_name = 'operator' - rel_name = row['relname'] - else: - type_name = types[type_str[0]] - else: - continue - - dependents.append( - { - 'type': type_name, - 'name': rel_name, - 'field': db_row['datname'] - } - ) - - # Release only those connections which we have created above. - if not is_connected: - manager.release(db_row['datname']) + self._temp_connection_check(rid, temp_conn, db_row, types, + dependents) + RoleView._release_connection(is_connected, manager, db_row) return dependents diff --git a/web/pgadmin/browser/server_groups/servers/tablespaces/__init__.py b/web/pgadmin/browser/server_groups/servers/tablespaces/__init__.py index c41406b..d392588 100644 --- a/web/pgadmin/browser/server_groups/servers/tablespaces/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/tablespaces/__init__.py @@ -467,6 +467,22 @@ class TablespaceView(PGChildNodeView): status=200 ) + def _format_privilege_data(self, data): + for key in ['spcacl']: + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = parse_priv_to_db( + data[key]['added'], self.acl + ) + if 'changed' in data[key]: + data[key]['changed'] = parse_priv_to_db( + data[key]['changed'], self.acl + ) + if 'deleted' in data[key]: + data[key]['deleted'] = parse_priv_to_db( + data[key]['deleted'], self.acl + ) + def get_sql(self, gid, sid, data, tsid=None): """ This function will genrate sql from model/properties data @@ -494,20 +510,7 @@ class TablespaceView(PGChildNodeView): old_data = self._formatter(old_data, tsid) # To format privileges data coming from client - for key in ['spcacl']: - if key in data and data[key] is not None: - if 'added' in data[key]: - data[key]['added'] = parse_priv_to_db( - data[key]['added'], self.acl - ) - if 'changed' in data[key]: - data[key]['changed'] = parse_priv_to_db( - data[key]['changed'], self.acl - ) - if 'deleted' in data[key]: - data[key]['deleted'] = parse_priv_to_db( - data[key]['deleted'], self.acl - ) + self._format_privilege_data(data) # If name is not present with in update data then copy it # from old data @@ -669,6 +672,61 @@ class TablespaceView(PGChildNodeView): status=200 ) + def _handel_dependents_type(self, types, type_str, row, rel_name): + type_name = '' + if types[type_str[0]] is None: + if type_str[0] == 'i': + type_name = 'index' + rel_name = row['indname'] + ' ON ' + rel_name + elif type_str[0] == 'o': + type_name = 'operator' + rel_name = row['relname'] + else: + type_name = types[type_str[0]] + return type_name, rel_name + + def _check_dependents_type(self, types, dependents, db_row, result): + for row in result['rows']: + rel_name = row['nspname'] + if rel_name is not None: + rel_name += '.' + + if rel_name is None: + rel_name = row['relname'] + else: + rel_name += row['relname'] + + type_str = row['relkind'] + # Fetch the type name from the dictionary + # if type is not present in the types dictionary then + # we will continue and not going to add it. + if type_str[0] in types: + # if type is present in the types dictionary, but it's + # value is None then it requires special handling. + type_name, rel_name = self._handel_dependents_type(types, + type_str, + row, + rel_name) + else: + continue + + dependents.append( + { + 'type': type_name, + 'name': rel_name, + 'field': db_row['datname'] + } + ) + + def _create_dependents_data(self, types, result, dependents, db_row, + is_connected, manager): + + self._check_dependents_type(types, dependents, db_row, result) + + # Release only those connections which we have created above. + if not is_connected: + manager.release(db_row['datname']) + def get_dependents(self, conn, sid, tsid): """ This function is used to fetch the dependents for the selected node. @@ -746,48 +804,8 @@ class TablespaceView(PGChildNodeView): if not status: current_app.logger.error(result) - for row in result['rows']: - rel_name = row['nspname'] - if rel_name is not None: - rel_name += '.' - - if rel_name is None: - rel_name = row['relname'] - else: - rel_name += row['relname'] - - type_name = '' - type_str = row['relkind'] - # Fetch the type name from the dictionary - # if type is not present in the types dictionary then - # we will continue and not going to add it. - if type_str[0] in types: - - # if type is present in the types dictionary, but it's - # value is None then it requires special handling. - if types[type_str[0]] is None: - if type_str[0] == 'i': - type_name = 'index' - rel_name = row['indname'] + ' ON ' + rel_name - elif type_str[0] == 'o': - type_name = 'operator' - rel_name = row['relname'] - else: - type_name = types[type_str[0]] - else: - continue - - dependents.append( - { - 'type': type_name, - 'name': rel_name, - 'field': db_row['datname'] - } - ) - - # Release only those connections which we have created above. - if not is_connected: - manager.release(db_row['datname']) + self._create_dependents_data(types, result, dependents, db_row, + is_connected, manager) return dependents diff --git a/web/pgadmin/browser/server_groups/servers/utils.py b/web/pgadmin/browser/server_groups/servers/utils.py index c402d08..a76c337 100644 --- a/web/pgadmin/browser/server_groups/servers/utils.py +++ b/web/pgadmin/browser/server_groups/servers/utils.py @@ -36,6 +36,48 @@ def parse_priv_from_db(db_privileges): return acl +def _check_privilege_type(priv): + if isinstance(priv['privileges'], dict) \ + and 'changed' in priv['privileges']: + tmp = [] + for p in priv['privileges']['changed']: + tmp_p = {'privilege_type': p['privilege_type'], + 'privilege': False, + 'with_grant': False} + + if 'with_grant' in p: + tmp_p['privilege'] = True + tmp_p['with_grant'] = p['with_grant'] + + if 'privilege' in p: + tmp_p['privilege'] = p['privilege'] + + tmp.append(tmp_p) + + priv['privileges'] = tmp + + +def _parse_privileges(priv, db_privileges, allowed_acls, priv_with_grant, + priv_without_grant): + _check_privilege_type(priv) + for privilege in priv['privileges']: + + if privilege['privilege_type'] not in db_privileges: + continue + + if privilege['privilege_type'] not in allowed_acls: + continue + + if privilege['with_grant']: + priv_with_grant.append( + db_privileges[privilege['privilege_type']] + ) + elif privilege['privilege']: + priv_without_grant.append( + db_privileges[privilege['privilege_type']] + ) + + def parse_priv_to_db(str_privileges, allowed_acls=[]): """ Common utility function to parse privileges before sending to database. @@ -66,41 +108,9 @@ def parse_priv_to_db(str_privileges, allowed_acls=[]): priv_with_grant = [] priv_without_grant = [] - if isinstance(priv['privileges'], dict) \ - and 'changed' in priv['privileges']: - tmp = [] - for p in priv['privileges']['changed']: - tmp_p = {'privilege_type': p['privilege_type'], - 'privilege': False, - 'with_grant': False} - - if 'with_grant' in p: - tmp_p['privilege'] = True - tmp_p['with_grant'] = p['with_grant'] - - if 'privilege' in p: - tmp_p['privilege'] = p['privilege'] + _parse_privileges(priv, db_privileges, allowed_acls, priv_with_grant, + priv_without_grant) - tmp.append(tmp_p) - - priv['privileges'] = tmp - - for privilege in priv['privileges']: - - if privilege['privilege_type'] not in db_privileges: - continue - - if privilege['privilege_type'] not in allowed_acls: - continue - - if privilege['with_grant']: - priv_with_grant.append( - db_privileges[privilege['privilege_type']] - ) - elif privilege['privilege']: - priv_without_grant.append( - db_privileges[privilege['privilege_type']] - ) # If we have all acl then just return all if len(priv_with_grant) == allowed_acls_len > 1: priv_with_grant = ['ALL'] @@ -182,6 +192,20 @@ def validate_options(options, option_name, option_value): return is_valid_options, valid_options +def _password_check(server, manager, old_key, new_key): + # Check if old password was stored in pgadmin4 sqlite database. + # If yes then update that password. + if server.password is not None: + password = decrypt(server.password, old_key) + + if isinstance(password, bytes): + password = password.decode() + + password = encrypt(password, new_key) + setattr(server, 'password', password) + manager.password = password + + def reencrpyt_server_passwords(user_id, old_key, new_key): """ This function will decrypt the saved passwords in SQLite with old key @@ -193,25 +217,7 @@ def reencrpyt_server_passwords(user_id, old_key, new_key): for server in Server.query.filter_by(user_id=user_id).all(): manager = driver.connection_manager(server.id) - # Check if old password was stored in pgadmin4 sqlite database. - # If yes then update that password. - if server.password is not None: - password = decrypt(server.password, old_key) - - if isinstance(password, bytes): - password = password.decode() - - password = encrypt(password, new_key) - setattr(server, 'password', password) - manager.password = password - elif manager.password is not None: - password = decrypt(manager.password, old_key) - - if isinstance(password, bytes): - password = password.decode() - - password = encrypt(password, new_key) - manager.password = password + _password_check(server, manager, old_key, new_key) if server.tunnel_password is not None: tunnel_password = decrypt(server.tunnel_password, old_key)