Fix race in Postgres user-list

Retrieve the user names and access rights
in a single query hence closing the race
window.

Change-Id: I66847827fa8cb0615c1662670017d2cc55466ff3
Closes-Bug: 1617464
This commit is contained in:
Petr Malik 2016-08-26 17:19:54 -04:00 committed by Amrith Kumar
parent 4c1c191def
commit 5c4b710000
4 changed files with 29 additions and 41 deletions

View File

@ -0,0 +1,4 @@
---
fixes:
- Close the race condition window in user-list
call. Closes-Bug 1617464

View File

@ -129,13 +129,15 @@ class UserQuery(object):
def list(cls, ignore=()):
"""Query to list all users."""
statement = "SELECT usename FROM pg_catalog.pg_user"
statement = (
"SELECT usename, datname, pg_encoding_to_char(encoding), "
"datcollate FROM pg_catalog.pg_user "
"LEFT JOIN pg_catalog.pg_database "
"ON CONCAT(usename, '=CTc/os_admin') = ANY(datacl::text[]) "
"WHERE (datistemplate ISNULL OR datistemplate = false)")
if ignore:
# User a simple tautology so all clauses can be AND'ed without
# crazy special logic.
statement += " WHERE 1=1"
for name in ignore:
statement += " AND usename != '{name}'".format(name=name)
for name in ignore:
statement += " AND usename != '{name}'".format(name=name)
return statement
@ -156,10 +158,7 @@ class UserQuery(object):
def get(cls, name):
"""Query to get a single user."""
return (
"SELECT usename FROM pg_catalog.pg_user "
"WHERE usename = '{name}'".format(name=name)
)
return cls.list() + " AND usename = '{name}'".format(name=name)
@classmethod
def create(cls, name, password, encrypt_password=None, *options):
@ -226,18 +225,6 @@ class UserQuery(object):
class AccessQuery(object):
@classmethod
def list(cls, user):
"""Query to list grants for a user."""
return (
"SELECT datname, pg_encoding_to_char(encoding), datcollate "
"FROM pg_database "
"WHERE datistemplate = false "
"AND 'user \"{user}\"=CTc/{admin}' = ANY (datacl)".format(
user=user, admin=PG_ADMIN)
)
@classmethod
def grant(cls, user, database):
"""Query to grant user access to a database."""

View File

@ -19,7 +19,6 @@ from trove.common import cfg
from trove.common import exception
from trove.common.i18n import _
from trove.guestagent.datastore.experimental.postgresql import pgutil
from trove.guestagent.db import models
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -78,17 +77,8 @@ class PgSqlAccess(object):
Return a list of serialized Postgres databases.
"""
if self.user_exists(username):
return [db.serialize() for db in self._get_databases_for(username)]
user = self._find_user(context, username)
if user is not None:
return user.databases
raise exception.UserNotFound(username)
def _get_databases_for(self, username):
"""Return all Postgres databases accessible by a given user."""
results = pgutil.query(
pgutil.AccessQuery.list(user=username),
timeout=30,
)
return [models.PostgreSQLSchema(
row[0].strip(), character_set=row[1], collate=row[2])
for row in results]

View File

@ -141,16 +141,23 @@ class PgSqlUsers(PgSqlAccess):
pgutil.UserQuery.list(ignore=cfg.get_ignored_users()),
timeout=30,
)
return [self._build_user(context, row[0].strip()) for row in results]
def _build_user(self, context, username):
names = set([row[0].strip() for row in results])
return [self._build_user(context, name, results) for name in names]
def _build_user(self, context, username, acl=None):
"""Build a model representation of a Postgres user.
Include all databases it has access to.
"""
user = models.PostgreSQLUser(username)
dbs = self.list_access(context, username, None)
for d in dbs:
user.databases.append(d)
if acl:
dbs = [models.PostgreSQLSchema(row[1].strip(),
character_set=row[2],
collate=row[3])
for row in acl if row[0] == username and row[1] is not None]
for d in dbs:
user.databases.append(d.serialize())
return user
def delete_user(self, context, user):
@ -199,7 +206,7 @@ class PgSqlUsers(PgSqlAccess):
)
if results:
return self._build_user(context, username)
return self._build_user(context, username, results)
return None