Here are what I did to set the privileges correctly:
1. Only allow the sameuser connection in pg_hba.conf
In pg_hba.conf file, instead of
host all all 127.0.0.1/32 md5
Set
host sameuser all 127.0.0.1/32 md5
host all postgres 127.0.0.1/32 md5
This allows all users on the localhost (connecting through TCP/IP) to be able to only connect to the database of the same name. postgres superuser can still connect to all databases.
While this is good enough, this only restricts the connectivity of the users - so if I decide to allow other connection methods, I need to remember to set the entries correctly. This seems somewhat scary, so I also did the next two.
2. Remove privileges from the databases themselves
By default, all users (PUBLIC) has at least CONNECT privilege assigned to all new databases (!) To see this, I did the following:
postgres=# select d.datname, d.datacl from postgres.pg_catalog.pg_database d;
datname | datacl
----------------+-------------------------------------
postgres |
dl_test |
dl |
template1 | {=c/postgres,postgres=CTc/postgres}
template0 | {=c/postgres,postgres=CTc/postgres}
(In the future, "\l+" command should suffice - http://archives.postgresql.org/pgsql-committers/2008-03/msg00552.php - but my pgsql doesn't seem to have this patch yet.) Blank datacl apparently means everyone can connect to it. So I need to revoke the privleges
postgres=# revoke all on database dl_test from PUBLIC;
REVOKE
postgres=# select d.datname, d.datacl from postgres.pg_catalog.pg_database d;
datname | datacl
----------------+-------------------------------------
dl_test | {dl=CTc/dl}
.. so PostgreSQL is smart enough to only leave out privileges for the owner user. Nice :)
After doing this to all of the databases, only the database owner would have permission to see her own database.
However, this still allows listing of the database names. In phpPgAdmin, this is worked around by setting 'owned_only' option. I haven't found a way to NOT be able to see this in the DB itself...
3. Change the permission of template1 database
I ran the same REVOKE command against template1 database. When a new database is created, contents of template1 is copied; so we don't have to do step 2 again after this.