mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-14 13:06:34 +03:00
Obscure the password on url dsn too
Note that we don't leak anymore the password length. Fix #528
This commit is contained in:
parent
103655d670
commit
9f160fd820
2
NEWS
2
NEWS
|
@ -7,6 +7,8 @@ What's new in psycopg 2.7.2
|
||||||
- Fixed inconsistent state in externally closed connections
|
- Fixed inconsistent state in externally closed connections
|
||||||
(:tickets:`#263, #311, #443`). Was fixed in 2.6.2 but not included in
|
(:tickets:`#263, #311, #443`). Was fixed in 2.6.2 but not included in
|
||||||
2.7 by mistake.
|
2.7 by mistake.
|
||||||
|
- Don't display the password in `connection.dsn` when the connection
|
||||||
|
string is specified as an URI (:ticket:`#528`).
|
||||||
|
|
||||||
|
|
||||||
What's new in psycopg 2.7.1
|
What's new in psycopg 2.7.1
|
||||||
|
|
|
@ -343,6 +343,9 @@ The ``connection`` class
|
||||||
Read-only string containing the connection string used by the
|
Read-only string containing the connection string used by the
|
||||||
connection.
|
connection.
|
||||||
|
|
||||||
|
If a password was specified in the connection string it will be
|
||||||
|
obscured.
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
pair: Transaction; Autocommit
|
pair: Transaction; Autocommit
|
||||||
|
|
|
@ -1248,20 +1248,53 @@ static struct PyGetSetDef connectionObject_getsets[] = {
|
||||||
|
|
||||||
/* initialization and finalization methods */
|
/* initialization and finalization methods */
|
||||||
|
|
||||||
static void
|
RAISES_NEG static int
|
||||||
obscure_password(connectionObject *conn)
|
obscure_password(connectionObject *conn)
|
||||||
{
|
{
|
||||||
char *pos;
|
PQconninfoOption *options;
|
||||||
|
PyObject *d = NULL, *v = NULL, *dsn = NULL;
|
||||||
|
char *tmp;
|
||||||
|
int rv = -1;
|
||||||
|
|
||||||
if (!conn || !conn->dsn) {
|
if (!conn || !conn->dsn) {
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = strstr(conn->dsn, "password");
|
if (!(options = PQconninfoParse(conn->dsn, NULL))) {
|
||||||
if (pos != NULL) {
|
/* unlikely: the dsn was already tested valid */
|
||||||
for (pos = pos+9 ; *pos != '\0' && *pos != ' '; pos++)
|
return 0;
|
||||||
*pos = 'x';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(d = psycopg_dict_from_conninfo_options(
|
||||||
|
options, /* include_password = */ 1))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
if (NULL == PyDict_GetItemString(d, "password")) {
|
||||||
|
/* the dsn doesn't have a password */
|
||||||
|
rv = 0;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scrub the password and put back the connection string together */
|
||||||
|
if (!(v = Text_FromUTF8("xxx"))) { goto exit; }
|
||||||
|
if (0 > PyDict_SetItemString(d, "password", v)) { goto exit; }
|
||||||
|
if (!(dsn = psycopg_make_dsn(Py_None, d))) { goto exit; }
|
||||||
|
if (!(dsn = psycopg_ensure_bytes(dsn))) { goto exit; }
|
||||||
|
|
||||||
|
/* Replace the connection string on the connection object */
|
||||||
|
tmp = conn->dsn;
|
||||||
|
psycopg_strdup(&conn->dsn, Bytes_AS_STRING(dsn), -1);
|
||||||
|
PyMem_Free(tmp);
|
||||||
|
|
||||||
|
rv = 0;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
PQconninfoFree(options);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
Py_XDECREF(d);
|
||||||
|
Py_XDECREF(dsn);
|
||||||
|
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -1303,7 +1336,12 @@ connection_setup(connectionObject *self, const char *dsn, long int async)
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
/* here we obfuscate the password even if there was a connection error */
|
/* here we obfuscate the password even if there was a connection error */
|
||||||
|
{
|
||||||
|
PyObject *ptype = NULL, *pvalue = NULL, *ptb = NULL;
|
||||||
|
PyErr_Fetch(&ptype, &pvalue, &ptb);
|
||||||
obscure_password(self);
|
obscure_password(self);
|
||||||
|
PyErr_Restore(ptype, pvalue, ptb);
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,8 @@ STEALS(1) HIDDEN PyObject * psycopg_ensure_text(PyObject *obj);
|
||||||
HIDDEN PyObject *psycopg_dict_from_conninfo_options(PQconninfoOption *options,
|
HIDDEN PyObject *psycopg_dict_from_conninfo_options(PQconninfoOption *options,
|
||||||
int include_password);
|
int include_password);
|
||||||
|
|
||||||
|
HIDDEN PyObject *psycopg_make_dsn(PyObject *dsn, PyObject *kwargs);
|
||||||
|
|
||||||
/* Exceptions docstrings */
|
/* Exceptions docstrings */
|
||||||
#define Error_doc \
|
#define Error_doc \
|
||||||
"Base class for error exceptions."
|
"Base class for error exceptions."
|
||||||
|
|
|
@ -280,6 +280,30 @@ exit:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Make a connection string out of a string and a dictionary of arguments.
|
||||||
|
*
|
||||||
|
* Helper to call psycopg2.extensions.make_dns()
|
||||||
|
*/
|
||||||
|
PyObject *
|
||||||
|
psycopg_make_dsn(PyObject *dsn, PyObject *kwargs)
|
||||||
|
{
|
||||||
|
PyObject *ext = NULL, *make_dsn = NULL;
|
||||||
|
PyObject *args = NULL, *rv = NULL;
|
||||||
|
|
||||||
|
if (!(ext = PyImport_ImportModule("psycopg2.extensions"))) { goto exit; }
|
||||||
|
if (!(make_dsn = PyObject_GetAttrString(ext, "make_dsn"))) { goto exit; }
|
||||||
|
|
||||||
|
if (!(args = PyTuple_Pack(1, dsn))) { goto exit; }
|
||||||
|
rv = PyObject_Call(make_dsn, args, kwargs);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
Py_XDECREF(args);
|
||||||
|
Py_XDECREF(make_dsn);
|
||||||
|
Py_XDECREF(ext);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
/* Convert a C string into Python Text using a specified codec.
|
/* Convert a C string into Python Text using a specified codec.
|
||||||
*
|
*
|
||||||
* The codec is the python function codec.getdecoder(enc). It is only used on
|
* The codec is the python function codec.getdecoder(enc). It is only used on
|
||||||
|
|
|
@ -1504,19 +1504,16 @@ class PasswordLeakTestCase(ConnectingTestCase):
|
||||||
def test_leak(self):
|
def test_leak(self):
|
||||||
self.assertRaises(psycopg2.DatabaseError,
|
self.assertRaises(psycopg2.DatabaseError,
|
||||||
self.GrassingConnection, "dbname=nosuch password=whateva")
|
self.GrassingConnection, "dbname=nosuch password=whateva")
|
||||||
|
self.assertDsnEqual(self.dsn, "dbname=nosuch password=xxx")
|
||||||
|
|
||||||
self.assert_('nosuch' in self.dsn)
|
@skip_before_libpq(9, 2)
|
||||||
self.assert_('password' in self.dsn)
|
|
||||||
self.assert_('whateva' not in self.dsn)
|
|
||||||
|
|
||||||
def test_url_leak(self):
|
def test_url_leak(self):
|
||||||
self.assertRaises(psycopg2.DatabaseError,
|
self.assertRaises(psycopg2.DatabaseError,
|
||||||
self.GrassingConnection,
|
self.GrassingConnection,
|
||||||
"postgres://someone:whateva@localhost/nosuch")
|
"postgres://someone:whateva@localhost/nosuch")
|
||||||
|
|
||||||
self.assert_('nosuch' in self.dsn)
|
self.assertDsnEqual(self.dsn,
|
||||||
self.assert_('someone' in self.dsn)
|
"user=someone password=xxx host=localhost dbname=nosuch")
|
||||||
self.assert_('whateva' not in self.dsn)
|
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
|
|
Loading…
Reference in New Issue
Block a user