Skip to content

Commit 4ff3ad8

Browse files
Added ConnectParams.parse_dsn_with_credentials() and associated tests to
ensure that the ability to use a dsn containing credentials works as expected (and corrected bug that was preventing this from happening).
1 parent f72338c commit 4ff3ad8

File tree

11 files changed

+122
-69
lines changed

11 files changed

+122
-69
lines changed

doc/src/api_manual/connect_param.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ ConnectParams Methods
3232
pairs, or a simple alias which is looked up in ``tnsnames.ora``. Parameters
3333
that are found in the connect string override any currently stored values.
3434

35+
.. method:: ConnectParams.parse_dsn_with_credentials(dsn)
36+
37+
Parses a dsn in the form <user>/<password>@<connect_string> or in the form
38+
<user>/<password> and returns a 3-tuple containing the parsed user, password
39+
and connect string. Empty strings are returned as the value ``None``. This is
40+
done automatically when a value is passed to the ``dsn`` parameter but no
41+
value is passed to the ``user`` password when creating a standalone
42+
connection or connection pool.
43+
3544
.. method:: ConnectParams.set(user=None, proxy_user=None, password=None, \
3645
newpassword=None, wallet_password=None, access_token=None, host=None, \
3746
port=None, protocol=None, https_proxy=None, https_proxy_port=None, service_name=None, \

doc/src/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,15 @@ Thick Mode Changes
6161
for a type containing an attribute or element with an unsupported data type
6262
until the first attempt to reference the attribute or element with the
6363
unsupported data type.
64+
#) Fixed bug when attempting to create bequeath connections using the DSN
65+
parameter with credentials.
6466

6567
Common Changes
6668
++++++++++++++
6769

6870
#) Improved type annotations.
71+
#) Added method for parsing a DSN with credentials by calling
72+
:meth:`ConnectParams.parse_dsn_with_credentials()`.
6973
#) Error ``DPY-2038: element at index {index} does not exist`` is now raised
7074
whenever an element in a database collection is missing. Previously, thick
7175
mode raised ``DPI-1024: element at index {index} does not exist`` and thin

src/oracledb/connect_params.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2021, 2022, Oracle and/or its affiliates.
2+
# Copyright (c) 2021, 2023, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -599,6 +599,17 @@ def parse_connect_string(self, connect_string: str) -> None:
599599
"""
600600
self._impl.parse_connect_string(connect_string)
601601

602+
def parse_dsn_with_credentials(self, dsn: str) -> tuple:
603+
"""
604+
Parses a dsn in the form <user>/<password>@<connect_string> or in the
605+
form <user>/<password> and returns a 3-tuple containing the parsed
606+
user, password and connect string. Empty strings are returned as the
607+
value None. This is done automatically when a value is passed to
608+
the dsn parameter but no value is passed to the user password when
609+
creating a standalone connection or connection pool.
610+
"""
611+
return self._impl.parse_dsn_with_credentials(dsn)
612+
602613
@utils.params_setter
603614
def set(self, *,
604615
user: str=None,

src/oracledb/connection.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -109,12 +109,7 @@ def __init__(self,
109109
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
110110
else:
111111
params_impl = params._impl.copy()
112-
if kwargs:
113-
params_impl.set(kwargs)
114-
if dsn is not None:
115-
dsn = params_impl.parse_dsn(dsn, thin)
116-
if dsn is None:
117-
dsn = params_impl.get_connect_string()
112+
dsn = params_impl.process_args(dsn, kwargs, thin)
118113

119114
# see if connection is being acquired from a pool
120115
if pool is None:

src/oracledb/impl/base/connect_params.pyx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -527,10 +527,7 @@ cdef class ConnectParamsImpl:
527527

528528
def get_connect_string(self):
529529
"""
530-
Internal method for getting the connect string. This will either be the
531-
connect string supplied to parse_connect_string() or parse_dsn(), or it
532-
will be a connect string built up from the components supplied when the
533-
object was built.
530+
Returns a connect string generated from the parameters.
534531
"""
535532
return self.description_list.build_connect_string()
536533

@@ -557,28 +554,28 @@ cdef class ConnectParamsImpl:
557554
errors._raise_err(errors.ERR_CANNOT_PARSE_CONNECT_STRING, cause=e,
558555
data=connect_string)
559556

560-
def parse_dsn(self, str dsn, bint parse_connect_string):
557+
def parse_dsn_with_credentials(self, str dsn):
561558
"""
562559
Parse a dsn (data source name) string supplied by the user. This can be
563-
in the form user/password@connect_string or it can be a simple connect
564-
string. The connect string is returned and the user, proxy_user and
565-
password values are retained.
560+
in the form user/password@connect_string or it can be in the form
561+
user/password. The user, password and connect string are returned in a
562+
3-tuple.
566563
"""
567-
connect_string = dsn
568564
pos = dsn.rfind("@")
569565
if pos >= 0:
570566
credentials = dsn[:pos]
571-
connect_string = dsn[pos + 1:]
572-
pos = credentials.find("/")
573-
if pos >= 0:
574-
user = credentials[:pos]
575-
self._set_password(credentials[pos + 1:])
576-
else:
577-
user = credentials
578-
self.parse_user(user)
579-
if parse_connect_string:
580-
self.parse_connect_string(connect_string)
581-
return connect_string
567+
connect_string = dsn[pos + 1:] or None
568+
else:
569+
credentials = dsn
570+
connect_string = None
571+
pos = credentials.find("/")
572+
if pos >= 0:
573+
user = credentials[:pos] or None
574+
password = credentials[pos + 1:] or None
575+
else:
576+
user = credentials or None
577+
password = None
578+
return (user, password, connect_string)
582579

583580
def parse_user(self, str user):
584581
"""
@@ -593,6 +590,30 @@ cdef class ConnectParamsImpl:
593590
else:
594591
self.user = user
595592

593+
def process_args(self, str dsn, dict kwargs, bint thin):
594+
"""
595+
Processes the arguments to connect() and create_pool().
596+
597+
- the keyword arguments are set
598+
- if no user was specified in the keyword arguments and a dsn is
599+
specified, it is parsed to determine the user, password and
600+
connect string and the user and password are stored
601+
- in thin mode, the connect string is then parsed into its
602+
components and stored
603+
- if no dsn was specified, one is built from the components
604+
- the connect string is returned
605+
"""
606+
if kwargs:
607+
self.set(kwargs)
608+
if self.user is None and dsn is not None:
609+
user, password, dsn = self.parse_dsn_with_credentials(dsn)
610+
self.set(dict(user=user, password=password))
611+
if dsn is not None and thin:
612+
self.parse_connect_string(dsn)
613+
if dsn is None:
614+
dsn = self.get_connect_string()
615+
return dsn
616+
596617

597618
cdef class Address:
598619
"""

src/oracledb/pool.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -84,16 +84,11 @@ def __init__(self, dsn: str=None, *,
8484
errors._raise_err(errors.ERR_INVALID_POOL_PARAMS)
8585
else:
8686
params_impl = params._impl.copy()
87-
if kwargs:
88-
params_impl.set(kwargs)
89-
self._connection_type = \
90-
params_impl.connectiontype or connection_module.Connection
9187
with driver_mode.get_manager() as mode_mgr:
9288
thin = mode_mgr.thin
93-
if dsn is not None:
94-
dsn = params_impl.parse_dsn(dsn, thin)
95-
if dsn is None:
96-
dsn = params_impl.get_connect_string()
89+
dsn = params_impl.process_args(dsn, kwargs, thin)
90+
self._connection_type = \
91+
params_impl.connectiontype or connection_module.Connection
9792
if thin:
9893
impl = thin_impl.ThinPoolImpl(dsn, params_impl)
9994
else:

tests/test_1100_connection.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,6 @@ def __verify_fetched_data(self, connection):
5454
fetched_data = [s for s, in cursor.execute(sql)]
5555
self.assertEqual(fetched_data, expected_data)
5656

57-
def __verify_args(self, connection):
58-
self.assertEqual(connection.username, test_env.get_main_user(),
59-
"user name differs")
60-
self.assertEqual(connection.dsn, test_env.get_connect_string(),
61-
"dsn differs")
62-
6357
def __verify_attributes(self, connection, attrName, value, sql):
6458
setattr(connection, attrName, value)
6559
cursor = connection.cursor()
@@ -70,7 +64,10 @@ def __verify_attributes(self, connection, attrName, value, sql):
7064
def test_1100_simple_connection(self):
7165
"1100 - simple connection to database"
7266
connection = test_env.get_connection()
73-
self.__verify_args(connection)
67+
self.assertEqual(connection.username, test_env.get_main_user(),
68+
"user name differs")
69+
self.assertEqual(connection.dsn, test_env.get_connect_string(),
70+
"dsn differs")
7471

7572
@unittest.skipIf(test_env.get_is_thin(),
7673
"thin mode doesn't support application context yet")
@@ -226,14 +223,6 @@ def test_1113_connect_with_handle(self):
226223
connection2.close)
227224
connection.close()
228225

229-
def test_1115_single_arg(self):
230-
"1115 - connection to database with user, password, DSN together"
231-
arg = "%s/%s@%s" % (test_env.get_main_user(),
232-
test_env.get_main_password(),
233-
test_env.get_connect_string())
234-
connection = test_env.get_connection(arg)
235-
self.__verify_args(connection)
236-
237226
def test_1116_version(self):
238227
"1116 - connection version is a string"
239228
connection = test_env.get_connection()

tests/test_4500_connect_params.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,5 +591,33 @@ def test_4537_no_connect_string(self):
591591
params.set(mode=oracledb.SYSDBA)
592592
self.assertEqual(params.get_connect_string(), None)
593593

594+
def test_4538_dsn_with_credentials_and_connect_string(self):
595+
"4538 - test parsing a DSN with credentials and a connect string"
596+
params = oracledb.ConnectParams()
597+
dsn = "my_user4538/my_password4538@localhost:1525/my_service_name"
598+
user, password, dsn = params.parse_dsn_with_credentials(dsn)
599+
self.assertEqual(user, "my_user4538")
600+
self.assertEqual(password, "my_password4538")
601+
self.assertEqual(dsn, "localhost:1525/my_service_name")
602+
603+
def test_4539_dsn_with_only_credentials(self):
604+
"4539 - test parsing a DSN with only credentials"
605+
params = oracledb.ConnectParams()
606+
dsn = "my_user4539/my_password4539"
607+
user, password, dsn = params.parse_dsn_with_credentials(dsn)
608+
self.assertEqual(user, "my_user4539")
609+
self.assertEqual(password, "my_password4539")
610+
self.assertEqual(dsn, None)
611+
612+
def test_4560_dsn_with_empty_credentials(self):
613+
"4560 - test parsing a DSN with empty credentials"
614+
dsns = ["", "/"]
615+
for dsn in ("", "/"):
616+
params = oracledb.ConnectParams()
617+
user, password, dsn = params.parse_dsn_with_credentials(dsn)
618+
self.assertEqual(user, None)
619+
self.assertEqual(password, None)
620+
self.assertEqual(dsn, None)
621+
594622
if __name__ == "__main__":
595623
test_env.run_test_cases()

utils/templates/connect_params.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2021, 2022, Oracle and/or its affiliates.
2+
# Copyright (c) 2021, 2023, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -112,6 +112,17 @@ def parse_connect_string(self, connect_string: str) -> None:
112112
"""
113113
self._impl.parse_connect_string(connect_string)
114114

115+
def parse_dsn_with_credentials(self, dsn: str) -> tuple:
116+
"""
117+
Parses a dsn in the form <user>/<password>@<connect_string> or in the
118+
form <user>/<password> and returns a 3-tuple containing the parsed
119+
user, password and connect string. Empty strings are returned as the
120+
value None. This is done automatically when a value is passed to
121+
the dsn parameter but no value is passed to the user password when
122+
creating a standalone connection or connection pool.
123+
"""
124+
return self._impl.parse_dsn_with_credentials(dsn)
125+
115126
@utils.params_setter
116127
def set(self, *,
117128
#{{ args_without_defaults }}

utils/templates/connection.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -107,12 +107,7 @@ def __init__(self,
107107
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
108108
else:
109109
params_impl = params._impl.copy()
110-
if kwargs:
111-
params_impl.set(kwargs)
112-
if dsn is not None:
113-
dsn = params_impl.parse_dsn(dsn, thin)
114-
if dsn is None:
115-
dsn = params_impl.get_connect_string()
110+
dsn = params_impl.process_args(dsn, kwargs, thin)
116111

117112
# see if connection is being acquired from a pool
118113
if pool is None:

0 commit comments

Comments
 (0)